Module avr_progmem::string

source ·
Expand description

String utilities

This module offers further utilities base on ProgMem to make working with strings in progmem more convenient.

The difficulty with strings is that normally they are either heap allocated (as with the std Rust String) or dynamically sized (as with str). To store a string in progmem, one needs first of all a fixed-sized storage variant of a str. One option is to use byte string literals (e.g. b"foobar"), however, for some reason those only accept ASCII and no Unicode. At some day, one might be able to to convert arbitrary string literals to byte arrays like this:

// Dose not compile as of 1.51, because `try_into` is not a const fn, yet
static WIKIPEDIA: [u8; 12] = "维基百科".as_bytes().try_into().unwrap();

However, for the time being, this module offers as a convenient workaround:

Working with Strings

To work with strings in progmem, this crate offers two APIs and two modes of operation, each with their own little tradeoff.

Operation Modes

When you want to use strings from progmem you have two options:

  • you can load them as whole from progmem into RAM and work with them essentially like stack-allocated &strs
  • or, you choose to load the strings char after char from progmem and work with them using a char-iterator.

The first mode of operation obviously allows you to use them everywhere, where you can use a &str, giving you high compatibility with other APIs. On the other hand, this comes at the cost of high RAM usage. So you must leave enough free RAM to fit all your string, thus the bigger, your biggest string is, the less RAM you must use statically. So, you might have to split your strings somehow to make them manageable.

The alternative is to only load just one char at a time. This obviously limits the amount of RAM that you need, independently of how big your strings are, allowing you to work with really huge strings. However, you no longer get a &str, any you have make do with a char iterator.

However, if you only need your strings to be printed in some way, the Display and ufmt::uDisplay traits implementations (the latter only if the ufmt crate feature is enabled) of PmString, might become very handy. These trait implementations only need the char-iterator so they are very economic with respect to RAM usage.

APIs

API-wise you can either:

  • define progmem statics via the progmem macro and use them all over your program,
  • or, you create single-use progmem strings via the progmem_str and progmem_display macro

The single-use macros are the most concise option, but also a rather special-case solution. progmem_str gives you are very temporary &str to an ad-hoc loaded progmem string, so you can only pass it to a function call and you need enough RAM to store it. On the other hand, progmem_display gives you just something that is impl Display + uDisplay, so you can just print it, but it has minimal RAM usage.

If need anything more flexible or fancy, you are probably best served creating a static via progmem macro.

Examples

Using PmString directly via the progmem macro:

use avr_progmem::progmem;
use avr_progmem::string::LoadedString;

progmem! {
    // A simple Unicode string in progmem, internally stored as fix-sized
    // byte array, i.e. a `PmString<18>`.
    static progmem string TEXT = "Hello 大賢者";
}

// You can load it all at once (like a `ProgMem`)
let buffer: LoadedString<15> = TEXT.load();
// and use that as `&str`
assert_eq!("Hello 大賢者", &*buffer);

// Or you load it one char at a time (limits RAM usage)
let chars_iter = TEXT.chars(); // impl Iterator<Item=char>
let exp = ['H', 'e', 'l', 'l', 'o', ' ', '大', '賢', '者'];
assert_eq!(&exp, &*Vec::from_iter(chars_iter));

// Or you use the `Display`/`uDisplay` impl on `PmString`
fn foo<W: ufmt::uWrite>(writer: &mut W) {
    #[cfg(feature = "ufmt")] // requires the `ufmt` crate feature
    ufmt::uwrite!(writer, "{}", TEXT);
}

Using the special literal in-line string macros progmem_str (yielding a &str) and progmem_display (yielding some impl Display + uDisplay):

use avr_progmem::progmem_str as F;
use avr_progmem::progmem_display as D;

fn foo<W: ufmt::uWrite>(writer: &mut W) {
    // In-line string as temporary `&str`
    writer.write_str(F!("Hello 大賢者"));

    // In-line string as some `impl Display + uDisplay`
    #[cfg(feature = "ufmt")] // requires the `ufmt` crate feature
    ufmt::uwrite!(writer, "{}", D!("Hello 大賢者"));
}

You can also use arbitrary &str-yielding expression, including loading huge strings from files, just don’t use PmString::load nor progmem_str with huge strings (because if it is bigger than 255 bytes, it will panic).

use avr_progmem::progmem;
use avr_progmem::progmem_display as D;

progmem! {
    // Text too large to fit in the RAM of a Arduino Uno
    static progmem string HUGE_TEXT = include_str!("../examples/test_text.txt");
}
println!("{}", HUGE_TEXT);

// In-line a huge string from a file
println!("{}", D!(include_str!("../examples/test_text.txt")));

Structs