ESP32 non_std and digital v2 pins and Rust

Hi,

I am new to Rust, probably count as new to embedded but really really old. That out of the way.

I have started on my journey of banning myself from C and C++ so wanted to use rust instead. I have done some rust and understand a few of the concepts but struggling to get going.

I wanted to start with just writing to a TFT as this is relatively easy on my ST32 environment. It's an SPI device but the interface I am using expects a embedded_hal::digital::v2::OutputPin which is from the embedded_hal but I am using the esp32c3-hal which only has hal::prelude::_embedded_hal_digital_v2_OutputPin

When I use this the build fails with

let cs = io.pins.gpio4.into_push_pull_output();

error[E0382]: use of moved value: `cs`
  --> gc9a01-test/src/main.rs:71:55
   |
58 |     let cs = io.pins.gpio4.into_push_pull_output();
   |         -- move occurs because `cs` has type `GpioPin<Output<esp32c3_hal::gpio::PushPull>, 4>`, which does not implement the `Copy` trait

I could

  • move to the other hal
  • move to std
    but I want to understand what people do as I would prefer to understand and fix the problem rather than avoid it.

Thanks all,

Promise to help when up to speed.

I'm also new to rust. But this error has all the smell of borrow checker issues, and not a problem of "which crate should I use".

The rust compiler is totally neurotic (with a very good reason, btw) regarding concurrent access to heap memory in between threads or even processes. So a variable that
holds data in the heap must have a clear "ownership" at all times: which block of code "owns"
that variable.

To even start to understand what is happening at your side, I would need the complete message,
that is typically composed of two parts, as the example below:

  --> src/main.rs:45:23
   |
39 | pub fn median(the_vector:Vec<u32>) -> (Vec<u32>,f64) {
   |               ---------- move occurs because `the_vector` has type `Vec<u32>`, which does not implement the `Copy` trait
...
45 |         return (the_vector, the_vector[vec_len / 2 ].into());
   |                 ----------  ^^^^^^^^^^ value borrowed here after move
   |                 |
   |                 value moved here

As you can see, you need to look at both places: the line where the variable is created, and the line where the error happened.

In my specific case the solution was collecting the vector[vec_len / 2 ] thing into another variable before returning the array.

// at this point my code owns 'the_vector, so I can invoke it's methods
let whatever = the_vector[vec_len/2].into();  

// at the moment I return it, I no longer have access to it. ownership was moved to the caller.
return (the_vector,whatever);

I recommend you look very carefully at the rust book chapter 4 regarding ownership and the chapter 10 regarding lifetimes: these are concepts that must be very well known if you want to do anything more complex than println!("Hello world") in rust.

https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html

If fact I think brown chapter is getting better at explaining this:

https://rust-book.cs.brown.edu/ch04-00-understanding-ownership.html

https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html

I will read,

Here is the full message.


error[E0382]: use of moved value: `cs`
  --> gc9a01-test/src/main.rs:71:55
   |
58 |     let cs = io.pins.gpio4.into_push_pull_output();
   |         -- move occurs because `cs` has type `GpioPin<Output<esp32c3_hal::gpio::PushPull>, 4>`, which does not implement the `Copy` trait
...
65 |         cs,
   |         -- value moved here
...
71 |     let interface = SPIDisplayInterface::new(spi, dc, cs);
   |                                                       ^^ value used here after move

And the code,

    let dc = io.pins.gpio3;
    let cs = io.pins.gpio4.into_push_pull_output();

    let spi = Spi::new(
        peripherals.SPI2,
        sclk,
        mosi,
        miso,
        cs,
        100u32.kHz(),
        SpiMode::Mode0,
        &clocks,
    );

    let interface = SPIDisplayInterface::new(spi, dc, cs);

I think what you are saying is in rust you have to assign something to something else before returning the value. Otherwise it may corrupt memory. Christmas Day in NZ so here is a C++ sort of thing to show what I thing you mean. Note written freehand no editor so may be syntactically incorrect.

// bad
const foo(std::string* thing) {
return thing->substring(1,2)
}

// Good
const foo(std::string* thing) {
auto myString2 = return thing->substring(1,2)
return myString2;
}

that's complete wrong, and also, it is more idiomatic not to write return at tail position.

embedded hal libraries uses the property of linear types (e.g. ownership) to model hardware resources. in your example, the variable cs represent a IO pin, and it can only be used by value (i.e. moved) once. your code tries to use it twice, first time to construct the Spi hal driver, second time to construct the SPIDisplayInterface driver.

I don't know how the SPIDisplayInterface is defined, but either its design is flawed, or you are misusing it.

you cannot let two different types to both own the IO pin, this kind of confliction is exactly the kind of errors that the rust type system can detect but C/C++ cannot.

Rust is all about ownership.

cs is created at the top: let cs = .... Now the cs variable owns the value (whatever gpio4.into_push_pull_output() returns).

Then cs is moved when Spi::new is called. Perhaps cs is now owned by spi, but the main point is the cs has been consumed by the Spi::new function call.

Therefore the cs variable in the code above is not usable after the call to Spi::new, and Rust prevents you from using it again. That's why you get an error when passing it to SPIDisplayInterface::new.

This prevents a situation where this value has two owners. This would be some sort of logic error. For example, you may need to create a different cs and pass it to SPIDisplayInterface::new.

More importantly, learning how Rust ownership works by trying to write an application like this, is probably a mistake. It is much better to learn by going through the book, which is considered required material for learning Rust. Rust is not like many other languages that can be learned by jumping in and doing something right off the bat. It is too different from other languages for that to work. This seems especially difficult if you are also new to embedded.

Generally I when it difficult to consume text to kearn. This results in two types of people.

A) people think I am lazy and wont do what they perceive they had to do.
B) people who understand that text is not always the best approach

YouTube has been a god send with worked examples and for me. I also need the why rather than the rule taken at face value. Getting an answer is great, you can move on, but will be there next time around with no why.

However, in this case, did read chapter 4 and it does nake some sence. Know exactly the problem. Been a year maybe since last rust so guess I am a little, I'm going to say it, rusty.

Thanks all for helping.

1 Like

Please feel free to ask any questions you have here!

I apologize if my reply didn't seem friendly. I understand that the book (and written materials) won't work for everyone.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.