- Primary focus: Memory handling.
TODO
- beware that vscode.dev can't display inferred types (of
letdefinitions), 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 + commashows 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
nightlyRust compiler- the actual solutions work with
stableRust. However, the test harness (with extras on top of of Exercism's tests) needsnightlyRust (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
VecorString, wherever possible. BothVecandStringauto 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::Optionor 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_stddesign 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
Copytrait. New to Rust? Atraitis similar to aninterfacein Java, avirtualabstract field-less class with virtual methods in C++, or aprotocolin some other languages.
- "move"-d
(or
- See also ownership, borrowing and lifetimes.
- alternatively, use
constgenerics, 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
constgeneric 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
implto 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