1

I am working with Rust on a Teensy 4.0 ( --target thumbv7em-none-eabihf ) which means I have to use #![no_std] .

I have some situations where I want to do different things depending on the position of a rotary switch.

The following is a toy example that illustrates a problem where I want to return one of a series of objects that implement a trait.

fn text_for(selector: i32) -> impl Fn()->Box<dyn Iterator<Item=char>> {
    match selector {
        1 => || {
            let rval : Box<dyn Iterator<Item=char>> = Box::new("author".chars());
            rval
        },
        _ => || {
            let rval : Box::<dyn Iterator<Item=char>> = Box::new(b"baseline".iter().map(|&b| (b) as char));
            rval
        }
    }
}

Unfortunately Box is not available in the no_std environment. I have seen references to an alloc crate (Is it possible to use Box with no_std?), but when I use extern crate alloc; use alloc::boxed::Box; the compiler complains

error: no global memory allocator found but one is required; link to std or add `#[global_allocator]` to a static item that implements the GlobalAlloc trait.

error: `#[alloc_error_handler]` function required, but not found.

note: Use `#![feature(default_alloc_error_handler)]` for a default error handler.

Attempting to use the alloc-cortex-m crate to use a CortexMHeap as the #[global_allocator] as suggested by lkolbly results in the following error

error[E0554]: `#![feature]` may not be used on the stable release channel
 --> /home/thoth/.cargo/registry/src/github.com-1ecc6299db9ec823/linked_list_allocator-0.8.11/src/lib.rs:1:41
  |
1 | #![cfg_attr(feature = "const_mut_refs", feature(const_mut_refs))]
  |                                         ^^^^^^^^^^^^^^^^^^^^^^^

How can I work with dyn instances of a trait in a no_std environment using stable rust?

Mutant Bob
  • 3,121
  • 2
  • 27
  • 52
  • 1
    The error message suggests you didn't set up an alloc error handler after pulling in the alloc crate. Have you tried doing that? – the8472 Dec 14 '21 at 16:53
  • To use a Box, you can make a global allocator either yourself or using a library like https://github.com/rust-embedded/alloc-cortex-m/blob/master/examples/global_alloc.rs – lkolbly Dec 14 '21 at 17:16
  • https://docs.rs/alloc-cortex-m/latest/alloc_cortex_m/ "Note that using this as your global allocator requires nightly Rust." – Chayim Friedman Dec 14 '21 at 20:15
  • 1
    You can use other allocator. See a list of allocators at https://lib.rs/keywords/allocator. – Chayim Friedman Dec 14 '21 at 20:17

2 Answers2

2

Here's an example of a global allocator, adapted from alloc-cortex-m without using the feature const_mut_refs of linked_list_allocator that requires nightly Rust.

Note: The code was not tested.

Cargo.toml:

[dependencies]
linked_list_allocator = { version = "0.9", default-features = false, features = ["use_spin"] }

lib.rs:

#![no_std]

mod allocator;

extern crate alloc;
use alloc::boxed::Box;

#[global_allocator]
static ALLOCATOR: allocator::CortexMHeap = unsafe { allocator::CortexMHeap::empty() };


#[entry]
fn main() -> ! {
    // Initialize the allocator BEFORE you use it
    let start = cortex_m_rt::heap_start() as usize;
    let size = 1024; // in bytes
    unsafe { ALLOCATOR.init(start, size) }

    // ...
}

// Your code using `Box` here.

allocator.rs:

use core::alloc::{GlobalAlloc, Layout};
use core::cell::RefCell;
use core::ptr::{self, NonNull};
use core::mem::MaybeUninit;

use cortex_m::interrupt::Mutex;
use linked_list_allocator::Heap;

pub struct CortexMHeap {
    heap: Mutex<RefCell<MaybeUninit<Heap>>>,
}

impl CortexMHeap {
    /// Crate a new UNINITIALIZED heap allocator
    ///
    /// # Safety
    ///
    /// You must initialize this heap using the
    /// [`init`](struct.CortexMHeap.html#method.init) method before using the allocator.
    pub const unsafe fn empty() -> CortexMHeap {
        CortexMHeap {
            heap: Mutex::new(RefCell::new(MaybeUninit::uninit())),
        }
    }

    fn heap(&self, cs: &cortex_m::interrupt::CriticalSection) -> &mut Heap {
        let heap = &mut *self.heap.borrow(cs).borrow_mut();
        // SAFETY: `init()` initializes this, and it's guaranteed to be called by preconditions of `empty()`.
        unsafe { &mut *heap.as_mut_ptr() }
    }

    /// Initializes the heap
    ///
    /// This function must be called BEFORE you run any code that makes use of the
    /// allocator.
    ///
    /// `start_addr` is the address where the heap will be located.
    ///
    /// `size` is the size of the heap in bytes.
    ///
    /// Note that:
    ///
    /// - The heap grows "upwards", towards larger addresses. Thus `end_addr` must
    ///   be larger than `start_addr`
    ///
    /// - The size of the heap is `(end_addr as usize) - (start_addr as usize)`. The
    ///   allocator won't use the byte at `end_addr`.
    ///
    /// # Safety
    ///
    /// Obey these or Bad Stuff will happen.
    ///
    /// - This function must be called exactly ONCE.
    /// - `size > 0`
    pub unsafe fn init(&self, start_addr: usize, size: usize) {
        cortex_m::interrupt::free(|cs| {
            let heap = &mut *self.heap.borrow(cs).borrow_mut();
            *heap = MaybeUninit::new(Heap::empty());
            (*heap.as_mut_ptr()).init(start_addr, size);
        });
    }

    /// Returns an estimate of the amount of bytes in use.
    pub fn used(&self) -> usize {
        cortex_m::interrupt::free(|cs| self.heap(cs).used())
    }

    /// Returns an estimate of the amount of bytes available.
    pub fn free(&self) -> usize {
        cortex_m::interrupt::free(|cs| self.heap(cs).free())
    }
}

unsafe impl GlobalAlloc for CortexMHeap {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        cortex_m::interrupt::free(|cs| {
            self.heap(cs)
                .allocate_first_fit(layout)
                .ok()
                .map_or(ptr::null_mut(), |allocation| allocation.as_ptr())
        })
    }

    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        cortex_m::interrupt::free(|cs| {
            self.heap(cs)
                .deallocate(NonNull::new_unchecked(ptr), layout)
        });
    }
}
Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
1

One option, shown in the other answer, is to allow the use of boxes by providing an allocator. But if you don't require allocations anywhere else in your code, it's not necessary to introduce them just for the sake of trait objects - you can also create trait objects by borrowing stack-allocated values.

That will require changing in the API so that the closure doesn't return the iterator, but passes it to a user-provided closure that consumes it. The consuming closure can iterate over it and extract useful data. For example:

fn text_for<F, R>(selector: i32) -> impl Fn(F) -> R
where
    F: Fn(&mut dyn Iterator<Item = char>) -> R,
{
    move |consume| {
        let (mut it1, mut it2);
        let it: &mut dyn Iterator<Item = char> = match selector {
            1 => {
                it1 = "author".chars();
                &mut it1
            }
            _ => {
                it2 = b"baseline".iter().map(|&b| b as char);
                &mut it2
            }
        };
        consume(it)
    }
}

You could use this version of text_for() like this:

fn main() {
    let with_text_iter = text_for(1);
    assert_eq!(
        // example consume function just collects chars into Vec
        with_text_iter(|it| it.collect::<Vec<_>>()),
        &['a', 'u', 't', 'h', 'o', 'r']
    );
}

Playground

user4815162342
  • 141,790
  • 18
  • 296
  • 355