add best practices first draft
This commit is contained in:
parent
c94a9b8d75
commit
c05149e5e4
2 changed files with 56 additions and 2 deletions
|
@ -2,6 +2,7 @@
|
||||||
** xref:basic_application.adoc[Basic application]
|
** xref:basic_application.adoc[Basic application]
|
||||||
** xref:project_structure.adoc[Project Structure]
|
** xref:project_structure.adoc[Project Structure]
|
||||||
** xref:new_project.adoc[Starting a new Embassy project]
|
** 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:layer_by_layer.adoc[Bare metal to async]
|
||||||
* xref:runtime.adoc[Executor]
|
* xref:runtime.adoc[Executor]
|
||||||
* xref:delaying_a_task.adoc[Delaying a Task]
|
* xref:delaying_a_task.adoc[Delaying a Task]
|
||||||
|
@ -11,7 +12,7 @@
|
||||||
* xref:bootloader.adoc[Bootloader]
|
* xref:bootloader.adoc[Bootloader]
|
||||||
|
|
||||||
* xref:examples.adoc[Examples]
|
* xref:examples.adoc[Examples]
|
||||||
* xref:developer.adoc[Developer]
|
* xref:developer.adoc[Developer Docs]
|
||||||
** xref:developer_stm32.adoc[Developer: STM32]
|
** xref:developer_stm32.adoc[Developer Docs: STM32]
|
||||||
* xref:embassy_in_the_wild.adoc[Embassy in the wild]
|
* xref:embassy_in_the_wild.adoc[Embassy in the wild]
|
||||||
* xref:faq.adoc[Frequently Asked Questions]
|
* xref:faq.adoc[Frequently Asked Questions]
|
||||||
|
|
53
docs/modules/ROOT/pages/best_practices.adoc
Normal file
53
docs/modules/ROOT/pages/best_practices.adoc
Normal 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
|
||||||
|
()
|
||||||
|
}
|
||||||
|
----
|
Loading…
Reference in a new issue