How can I make a wrapper function for a STM32 HAL library?

Hi,

I am new to Rust, and I have a question about how I can create a function that wraps up 'block!(tx.write( b'\r' )).ok();' specifically in the code below.

fn main() -> ! {
//...
    let serial = Serial::usart2( p.USART2, (tx, rx), 115_200.bps(), clocks, &mut rcc.apb1r1 );
    let ( mut tx, mut _rx ) = serial.split();

    block!(tx.write( b'\r' )).ok();
//...
}

The code in the above is from an example of STM32 HAL, I would have made a function that takes a reference to either serial or tx to call tx.write( ) inside something like :

fn send_byte<T>( tx : &mut T, byte : u8 ) {
    block!(tx.write( byte )).ok();
}

However, for both being generic types, it's so difficult to implement such a wrapper due mainly to my lack of experience and knowledge in Rust.

Here's additional information:

Can anyone suggest a simple way to do that?

Thanks,

please be specific or provide a link. there are a many different flavored HALs for stm32, and each has their own opinionated style of doing things.

without any information about the library, I would suggest the "extract function" code action of rust-analyzer. check your editor's document on how to activate LSP code actions, for example, in vscode default key binding is CTRL . ("ctrl" and "dot"), you select the line and trigger the code action, there' should be a pop up menu with an entry of "extract into function" or similar.

Here's the link to the example source code in GitHub:

Also, I've tried this,

use crate::hal::serial;
//...
fn send_bytes_wait<T: serial::Write<u8>>( tx: T, byte: u8) {
    tx.write( b'\n' );
}

but what the compiler says is:

84 | fn send_bytes_wait<T: serial::Write<u8>>( tx: T, byte: u8) {
   |                               ^^^^^ private trait

As it indicates, it seems like trying to make use of that trait itself is not allowed being a private one. Now I wonder if there will be any realistic simple solution to what I want to have.

Looking back at them again, I don't understand why trying to call write() through a separate function, which I think does make sense, isn't allowed, while it's allowed to call tx.write() in the main. That may just be due to lack of knowledge though. But compared to C++, it's weird since it's like calling protected method where it's created is allowed but not through a function elsewhere.

ok, so it's this stm32l4xx crate. TLDR in this case: use embedded_hal::serial::Write as bounds for the tx.write() method.


how to create a wrapper anyway

from the document, the return type of serial.split() is (Tx<_>, Rx<_>) where the Tx<_> and Rx<_> types are parameterized with a marker type to select the corresponding hardware peripheral.

from the definition of Tx type, the selector/marker types doesn't have any trait bounds:

so if you want to take a Tx<_> argument, just need a generic type without any bounds, something like:

fn send<S>(tx: &mut Tx<S>) {
}

but you cannot do anything with this type signature, because the methods are implemented directly on concrete type Tx<USART1> and Tx<USART2>, not generically. so you must also implement your function over the concrete types directly.

but it's still possible if you insist, it's just a little bit complicated.

unlike c++, rust doesn't allow overloading function name, nor does it allow a generic function to have specialized implementation on concrete type, i.e. the following is INVALID:

fn send_byte<S>(tx: &mut Tx<S>, byte: u8);
fn send_byte<>(tx: &mut Tx<USART1>, byte: u8) {}
fn send_byte<>(tx: &mut Tx<USART2>, byte: u8) {}

but you can define your wrapper "function" as an associated function on traits, and implement the trait on the types:

// this is like to declare a function which can be overloaded over the `Self` type
trait SendByte {
    fn send_byte(&mut self, byte: u8);
}
// implementation
impl SendByte for Tx<USART1> {
    fn send_byte(&mut self, byte: u8) {
        // here we have concrete Self: Tx<USART1>
        // so we can do whatever the type supports
        block!(self.write(byte)).ok();
    }
}
impl SendByte for Tx<USART2> {
    //...
}

if you like, you can provide a plain (i.e. non-associated) function wrapper using the trait we just defined above, something like this:

fn send_byte<Tx: SendByte>(tx: &mut Tx, byte: u8) {
    tx.send_byte(byte)
}
1 Like

that is not a private trait defined in that module, it's a an (privately) imported trait from an external crate embedded-hal:

it is not re-exported (i.e. use vs pub use) so if you want to use that trait, you'll have to import the embedded-hal::serial::Write instead.

just be sure to use the same version as the one used by this stm32 crate.

1 Like

The way you suggested worked fine for me!

fn main() -> ! {

//...
   loop {
      //...
      send_byte( &mut tx, num );
      //...
   }
}

//...
trait SendByte {
    fn send_byte(&mut self, byte: u8);
}

impl SendByte for Tx<USART2> {
    fn send_byte(&mut self, byte: u8) {
        // here we have concrete Self: Tx<USART1>
        // so we can do whatever the type supports
        block!(self.write(byte)).ok();
    }
}

fn send_byte<Tx: SendByte>(tx: &mut Tx, byte: u8) {
    tx.send_byte(byte);
}

Thank you so much for your help!!

the trait and associated function is only needed if you want to provide a unified (overloaded, or feels like "generic") function name for multiple implementations. if you only ever need a wrapper for the single Tx<USART2> type, you can use it directly, because it's a concrete type (not generic).

what I described is a common trick to emulate function name overloading (similar to C++), the most common case is to dispatch on the type of a single argument. it makes sense in this particular situation, because how the Tx<_> types are only implemented over concrete parameter types, but I do not recommend to use this trick for arbitrary "overloading" of unrelated types.

1 Like

Okay, I will try to keep that in mind. Maybe I can get to the real meaning of what you've said more working on Rust going forward. Thank you again for the help!