Merge pull request #2265 from JuliDi/docs-best-practices

[Docs] Add best practices page
This commit is contained in:
Dario Nieuwenhuis 2023-12-08 20:49:45 +01:00 committed by GitHub
commit 4e7b855b2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 56 additions and 2 deletions

View file

@ -2,6 +2,7 @@
** xref:basic_application.adoc[Basic application]
** xref:project_structure.adoc[Project Structure]
** xref:new_project.adoc[Starting a new Embassy project]
** xref:best_practices.adoc[Best Practices]
* xref:layer_by_layer.adoc[Bare metal to async]
* xref:runtime.adoc[Executor]
* xref:delaying_a_task.adoc[Delaying a Task]
@ -11,7 +12,7 @@
* xref:bootloader.adoc[Bootloader]
* xref:examples.adoc[Examples]
* xref:developer.adoc[Developer]
** xref:developer_stm32.adoc[Developer: STM32]
* xref:developer.adoc[Developer Docs]
** xref:developer_stm32.adoc[Developer Docs: STM32]
* xref:embassy_in_the_wild.adoc[Embassy in the wild]
* xref:faq.adoc[Frequently Asked Questions]

View file

@ -0,0 +1,53 @@
= Best Practices
Over time, a couple of best practices have emerged. The following list should serve as a guideline for developers writing embedded software in _Rust_, especially in the context of the _Embassy_ framework.
== Passing Buffers by Reference
It may be tempting to pass arrays or wrappers, like link:https://docs.rs/heapless/latest/heapless/[`heapless::Vec`], to a function or return one just like you would with a `std::Vec`. However, in most embedded applications you don't want to spend ressources on an allocator and end up placing buffers on the stack.
This, however, can easily blow up your stack if you are not careful.
Consider the following example:
[,rust]
----
fn process_buffer(mut buf: [u8; 1024]) -> [u8; 1024] {
// do stuff and return new buffer
for elem in buf.iter_mut() {
*elem = 0;
}
buf
}
pub fn main() -> () {
let buf = [1u8; 1024];
let buf_new = process_buffer(buf);
// do stuff with buf_new
()
}
----
When calling `process_buffer` in your program, a copy of the buffer you pass to the function will be created,
consuming another 1024 bytes.
After the processing, another 1024 byte buffer will be placed on the stack to be returned to the caller.
(You can check the assembly, there will be two memcopy operations, e.g., `bl __aeabi_memcpy` when compiling for a Cortex-M processor.)
*Possible Solution:*
Pass the data by reference and not by value on both, the way in and the way out.
For example, you could return a slice of the input buffer as the output.
Requiring the lifetime of the input slice and the output slice to be the same, the memory safetly of this procedure will be enforced by the compiler.
[,rust]
----
fn process_buffer<'a>(buf: &'a mut [u8]) -> &'a mut[u8] {
for elem in buf.iter_mut() {
*elem = 0;
}
buf
}
pub fn main() -> () {
let mut buf = [1u8; 1024];
let buf_new = process_buffer(&mut buf);
// do stuff with buf_new
()
}
----