diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 2154799f3..fabb80e31 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -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] diff --git a/docs/modules/ROOT/pages/best_practices.adoc b/docs/modules/ROOT/pages/best_practices.adoc new file mode 100644 index 000000000..1e02f9ba9 --- /dev/null +++ b/docs/modules/ROOT/pages/best_practices.adoc @@ -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 + () +} +----