- Primary focus: Memory handling.
TODO
- beware that vscode.dev can't display inferred types (of
let
definitions), neither declared parameter names in function calls - vote for enabling rust-analyzer on vscode.dev
- https://github.dev/peter-kehl/no_std_rna_slice_patterns
- VS-Code-like horizontal menu (if you have it hidden by default, toggle it with "Alt")
- your VS Code theme (if you use VS Code sync)
Ctrl + comma
shows VS-Code-like Settings
- Alternative: Online VS Code:
- https://vscode.dev/github/peter-kehl/no_std_rna_slice_patterns
- Insiders (beta-like version)
- these don't have a horizontal menu, but open it by the hamburger button
nightly
Rust compiler- the actual solutions work with
stable
Rust. However, the test harness (with extras on top of of Exercism's tests) needsnightly
Rust (as of mid 2022).
- 00_utils
- 00_test_harness TODO other files
These examples are ordered as they progress:
- 01_on_heap-bytes-own_mut-string
- 02_no_heap-chars-own_mut-array-const_overall-limit
- 03_no_heap-array-const_limit-bytes-wipe_on_mut
- 04_no_heap-array-const_limit-bytes-wipe_on_clone
- 05_no_heap-array-const_limit-bytes-wipe_on_clone-unsafe (*)
- 06_no_heap-array-const_generic_exact-bytes
- 07_no_heap-array-const_generic_limit-bytes-wipe_on_mut
- 08_no_heap-array-const_generic_limit-bytes-wipe_on_clone
- 09_no_heap-slice-pass_in-bytes-wipe_on_mut
- 10_no_heap-slice-pass_in-bytes-wipe_on_drop
- 11_ok_heap-slices-box_dyn_trait-map
- 12_no_heap-slices-iterator_enum
- 13_no_heap-slices-iterator_impl
- 14_no_heap-eq_branch_iterators-dyn_trait
- 15_no_heap-eq_branch_iterators-matrix
- 16_no_heap-eq_iterator_to_generic_fn
- 17_no_heap-eq_dispatch_specialized
- 18_no_heap-eq_dispatch_universal
(*) 05_no_heap-array-const_limit-bytes-wipe_on_clone-unsafe
(as compared to the previous
implementation) doesn't introduce anything new related to no_std
, but it fits here. (Also,
unsafe
is more likely to be used with no_std
anyway).
Property | 01 | 02 | 03 | 04 (*) | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Implemented | ✓ | ✓ | ✓ | ✓ | ✓ | part | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
extra work for securing observability/serialization: array or passed-in mutable slice could leak data! | ||||||||||||||||
fixed compile-time size limit for all instances (one type) | ✓ | ✓ | ✓ | |||||||||||||
various compile-time size limits per instance (generic type), can have a default limit | ✓ | ✓ | ||||||||||||||
no compile-time limit | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |||||
random access & data storage (rather than sequential access only) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||||||||
refer to the original (with a lifetime) (rather than copy the original) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||||||||
refer to extra mutable storage (with a lifetime) | ✓ | ✓ | ||||||||||||||
non-obvious or impractical API (*) | 05 & 07 & 08 | |||||||||||||||
TODO mutable storage at/just before initiation, but then immutable -> shareable? | ||||||||||||||||
mutable & unlimited resize | ||||||||||||||||
--> TODO implement mutation for # 01 | ||||||||||||||||
mutable & limited resize | ||||||||||||||||
mutable, but no ability to resize | ||||||||||||||||
immutable | ||||||||||||||||
extra handling of strings/UTF-8 (*) | 🛆 | 🛆 | 🛆🛆 | 🛆 | 🛆 | |||||||||||
Clone is derive -d |
||||||||||||||||
Clone is implemented |
||||||||||||||||
no Clone -- needed at all? |
||||||||||||||||
Copy |
||||||||||||||||
different types for mutation and for sharing: 07 & 08 | ||||||||||||||||
minor unsafe code |
||||||||||||||||
--- | ||||||||||||||||
dyn (virtual) dispatch <-> static (compile-time) dispatch |
||||||||||||||||
extra dispatch methods | ||||||||||||||||
thread safe (Sync - if used in std ) -> TODO "tests": struct EnsureSync<T: Sync>; type _EnsureSyncRna = EnsureSync<Rna>; |
- All "unlimited" properties are constrained by available memory.
- (*) indicates a property or implementation that isn't
no_std
-specific, or is specific to this workspace. It's here for clarification.
TODO Group implementations?
Replace use of HashSet/HashMap
with BTreeSet/BTreeMap
when possible. Or create different data
structures. Or use 3rd party crates.
Replace Arc
with Rc
(since there is no multi-threading in no_std
).
-
Have your functions accept slices (shared or mutable), rather than
Vec
orString
, wherever possible. BothVec
andString
auto cast/expose themselves as a slice. (This is a good practice even with heap, or instd
.) -
Similarly, whenever possible, have your
struct
-s andenum
-s store references, or slices, rather than own the data. It does involvelifetimes
, but that can be a good practice, too. -
Can't
core::iter::Iterator.collect()
.- Even though
collect()
does exist incore
(and not only instd
), it can collect only to implementations ofcore::iter::FromIterator
. (That, again, exists incore
, in addition tostd
). However, there are nocore
-only implementors ofFromIterator
(other than collecting zero or one item tocore::option::Option
or tocore::result::Result
). collect()
doesn't exist for arrays nor slices (without heap). Hence we need to iterate and store in a (mutable) array or slice. New to Rust? And Worried about side effects? Good news: Safe Rust prevents unintended side effects, because we "cannot have a mutable reference while we have an immutable one to the same value."
- Even though
-
there is no dynamic/resizable data storage
- a
no_std
design needs to batch/buffer/limit the total data - use slices (instead of arrays) as parameter types wherever possible
- design function as accepting (shared or mutable) slices
- functions may need to write to mutable slice parameters (instead of returning).
- a
- New to Rust? Mutating slices or references/arrays may sound less "functional". But, in Rust any
mutated parameters must be declared so. Any parameter that may be modified must be either
borrowed
(passed) as a mutable reference or slice (which has exclusive access), or- passing ownership of the object:
- "move"-d
(or
clone()
-d first and then the clone is moved), or - [copied: Ownership > Stack-Only Data: Copy]
](https://doc.rust-lang.org/nightly/book/ch04-01-what-is-ownership.html#stack-only-data-copy)
if it implements
Copy
trait. New to Rust? Atrait
is similar to aninterface
in Java, avirtual
abstract field-less class with virtual methods in C++, or aprotocol
in some other languages.
- "move"-d
(or
- See also ownership, borrowing and lifetimes.
- alternatively, use
const
generics, a subset of Rust generics, for both function parameters and return values- make the array size (which has to be known in compile time) a
const
generic parameter - beware that generics make the executable larger, and the build process takes longer; it helps to
combine (
const
) generics for some functions, and slices for other
- make the array size (which has to be known in compile time) a
- application's top level function(s) define the array(s), or array-containing structs/enums, on stack. Then they call the processing functions with slices, or with const generic-sized arrays (or their references)
- this way you can re-use the same processing functions
- if you can process the incoming data last in, first out (in LIFO/stack order), you could recurse (possibly batching the data in an array at every recursion level)
- Have functions return an iterator wherever possible. (And use it for parameters, too. Again, a
good practice even in
std
.)- may need to implement core::iter::Iterator to represents results of your transformation. Such iterators refer to the underlying iterator (data source/origin) and they may keep some state on top of it (a state machine).
- You may want to combine/chain functions accepting and returning iterators. Use keyword
impl
to define types likeimpl Iterator<Item = YourItemType>
.
When developing for no_heap
, we can't collect()
from iterators. That makes some tasks that need
random access (access to all items at the same time, like sorting) difficult.
This would need limit on number of items to be known at build time. Then the caller would pass a
(mutable) reference to an array, or a slice, where we would manually collect the items (in a for
loop, or .foreach()
closure).
For some purposes we may not need access to all items if we don't need to access the whole collection. Then all we do needs sequential access only, for example a one-to-one transformation, and/or filtering.
For that we may need to implement a compound iterator. It would contain an underlying iterator (or several iterators) over the source data. It iterates and transforms and/or filters the source data.
Exercism Rust track > RNA Transcription
In addition to the text of the assignment, it helps to see the
tests
(or the same tests used for no_std
version)
https://github.com/peter-kehl/x-rust/blob/main/rust/rna-transcription-std/src/lib.rs
https://github.com/peter-kehl/x-rust/blob/main/rust/rna-transcription-no_std-no_heap/src/lib.rs