MaybeUninit<u8>

The call socket2::Socket::recv_from() takes an MaybeUninit. I can't do anything with this and can't find anything that tells me how to convince the compiler it is initialised after a successful read. Any help appreciated.

Try the offcial docs.

1 Like

Also, what exactly are you trying to do?

I did try various thing from the examples in the docs (which I should have mentioned) but failed to find the correct syntax.
So in my struct I define the array as:

udp_frame : [MaybeUninit<u8>; common_defs::FRAME_SZ],

and in the impl new() initialise it as:

udp_frame: unsafe {MaybeUninit::uninit().assume_init()},

then in the read:

let r = self.p_sock.recv_from(&mut self.udp_frame);

So far so good I get data of 1032 bytes in the frame. Now I need to decode. So this is the first level of decode:

if self.udp_frame[3] == common_defs::EP6 {
            // We have a frame of IQ data
            // First 8 bytes are the header, then 2x512 bytes of data
            // The sync and cc bytes are the start of each data frame
            //
            // Extract and check the sequence number
            //  2    1   1   4
            // Sync Cmd End Seq
            // if the sequence number check fails it means we have missed some frames
            // Nothing we can do so it just gets reported.
            let mut j: usize = 0;
            let mut ep6_seq : [u8; 4];
            for b in self.udp_frame[4..8] {
                ep6_seq[j] = b;
            }
            self.i_seq.check_ep6_seq(self.udp_frame[4..8] as [u8; 4]);

			// For 1,2 radios the entire dataframe is used
			// For 3 radios there are 4 padding bytes in each frame
            // TBD: For now fix num_rx at one as we don't have the data yet
            let num_rx = 1; 
            let mut end_frame_1 = common_defs::END_FRAME_1;
            let mut end_frame_2 = common_defs::END_FRAME_2;
            let mut data_sz = common_defs::PROT_SZ * 2;
            let mut num_smpls = common_defs::NUM_SMPLS_1_RADIO;
            if num_rx == 2 {
                num_smpls = common_defs::NUM_SMPLS_2_RADIO;
            } else if num_rx == 3 {
                num_smpls = common_defs::NUM_SMPLS_3_RADIO;
                end_frame_1 -= 4;
                end_frame_2 -= 4;
                data_sz -= 8;
            }

            // Extract the data from the UDP frame into the protocol frame
            j = 0;
            for b in common_defs::START_FRAME_1..end_frame_1 {
                self.udp_frame[j as usize] = b;
                j += 1;
            }
            j = 0;
            for b in common_defs::START_FRAME_2..end_frame_2 {
                self.udp_frame[j as usize] = b;
                j += 1;
            }

        } else if self.udp_frame[3] == common_defs::EP4 {
            // We have wideband data
            // TBD
        }
        protocol::decoder::frame_decode(126, 1, 48000, common_defs::FRAME_SZ*2, self.prot_frame);

The compiler objects to any attempt to extract data from the frame even ==. I will look in more detail at the docs again and see if I can work out what to do if you tell me the answer is there I will try and work it out myself.

This doesn't initialize anything and is instant undefined behaviour. The reason why Socket::recv_from takes an &[MaybeUninit<u8>] since that is roughly what is expected by read(2). It also marks to the compiler that the bytes in that array are un-initialized and no assumptions must be made about them.
You want to initialize udp_frame as udp_frame: [MaybeUninit::uninit(); common_defs::FRAME_SZ]. Then you can call let r = self.p_sock.recv_frame(self.udp_frame.as_mut()). Finally, since recv_frame initializes the bytes, you can call slice_assume_init() (if you're on nightly) or call assume_init() for first r bytes.
Or you can simply avoid all this and use the Read implementation for Socket.

What's the error?

It's not, pretty much as a special-case, and it's explicitly mentioned in the documentation:

MaybeUninit<T> can be used to initialize a large array element-by-element:

    use std::mem::{self, MaybeUninit};

    // Create an uninitialized array of `MaybeUninit`. The `assume_init` is
    // safe because the type we are claiming to have initialized here is a
    // bunch of `MaybeUninit`s, which do not require initialization.
    let mut data: [MaybeUninit<Vec<u32>>; 1000] = unsafe {
        MaybeUninit::uninit().assume_init()
    };

I'm guessing this is a stable workaround for uninit_array() being nightly-only at the moment.

3 Likes

I'm using socket2 because socket does not have the required options to set buffer size. Unfortunately socket2::Socket has no method recv_frame() and my udp_frame has no method assume_init() so I didn't get far with that. I put the bits in that would fit but the code is pretty confused now and throws loads of errors.

error[E0369]: binary operation == cannot be applied to type MaybeUninit<u8>
--> src\udp\udp_man\udp_reader.rs:117:30
|
117 | if self.udp_frame[3] == common_defs::EP6 {
| ----------------- ^^ ---------------- u8
| |
| MaybeUninit

error[E0277]: the size for values of type [MaybeUninit<u8>] cannot be known at compilation time
--> src\udp\udp_man\udp_reader.rs:129:22
|
129 | for b in self.udp_frame[4..8] {
| ^^^^^^^^^^^^^^^^^^^^ expected an implementor of trait IntoIterator
|
= note: the trait bound [MaybeUninit<u8>]: IntoIterator is not satisfied
= note: required because of the requirements on the impl of IntoIterator for [MaybeUninit<u8>]
help: consider borrowing here
|
129 | for b in &self.udp_frame[4..8] {
| +
129 | for b in &mut self.udp_frame[4..8] {
| ++++

error[E0277]: [MaybeUninit<u8>] is not an iterator
--> src\udp\udp_man\udp_reader.rs:129:22
|
129 | for b in self.udp_frame[4..8] {
| ^^^^^^^^^^^^^^^^^^^^ expected an implementor of trait IntoIterator
|
= note: the trait bound [MaybeUninit<u8>]: IntoIterator is not satisfied
= note: required because of the requirements on the impl of IntoIterator for [MaybeUninit<u8>]
help: consider borrowing here
|
129 | for b in &self.udp_frame[4..8] {
| +
129 | for b in &mut self.udp_frame[4..8] {
| ++++

error[E0308]: mismatched types
--> src\udp\udp_man\udp_reader.rs:154:46
|
154 | self.udp_frame[j as usize] = b;
| -------------------------- ^ expected union MaybeUninit, found u32
| |
| expected due to the type of this binding
|
= note: expected union MaybeUninit<u8>
found type u32

error[E0308]: mismatched types
--> src\udp\udp_man\udp_reader.rs:159:46
|
159 | self.udp_frame[j as usize] = b;
| -------------------------- ^ expected union MaybeUninit, found u32
| |
| expected due to the type of this binding
|
= note: expected union MaybeUninit<u8>
found type u32

error[E0369]: binary operation == cannot be applied to type MaybeUninit<u8>
--> src\udp\udp_man\udp_reader.rs:163:37
|
163 | } else if self.udp_frame[3] == common_defs::EP4 {
| ----------------- ^^ ---------------- u8
| |
| MaybeUninit

error[E0308]: mismatched types
--> src\udp\udp_man\udp_reader.rs:167:81
|
167 | protocol::decoder::frame_decode(126, 1, 48000, common_defs::FRAME_SZ*2, self.prot_frame);
| ^^^^^^^^^^^^^^^ expected union MaybeUninit, found u8
|
= note: expected array [MaybeUninit<u8>; 1032]
found array [u8; 1008]

error[E0605]: non-primitive cast: [MaybeUninit<u8>] as [u8; 4]
--> src\udp\udp_man\udp_reader.rs:132:38
|
132 | self.i_seq.check_ep6_seq(self.udp_frame[4..8] as [u8; 4]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ an as expression can only be used to convert between primitive types or to coerce to a specific trait object

Some errors have detailed explanations: E0277, E0308, E0369, E0605.
For more information about an error, try rustc --explain E0277.
warning: rust_console (bin "rust_console") generated 5 warnings
error: could not compile rust_console due to 8 previous errors; 5 warnings emitted

The syntax foo[a..b] creates an unsized slice [T], which cannot be used directly, only behind a pointer. Even though you use a constant range 4..8, Rust doesn't know that the result is a fixed-sized array of length 4. This means that you must always take a & or &mut to the slice, i.e.

for b in &self.udp_frame[4..8] { .. }

Don't forget that b is now a reference, and you may need to dereference it within the loop body.

common_defs::EP6 is likely a u8, which is different from MaybeUninit<u8> on the left side. Also, comparison operators are not implemented on MaybeUninit<T>, since the value may be uninitialized and invalid to read. Note that MaybeUninit<T> is a proper type, and not some compiler magic, so you should deal with it as with any other type: write an explicit conversion to the required type. In this case, it is MaybeUninit::assume_init. Remember, make sure that the value actually is fully initialized, otherwise calling assume_init is instant Undefined Behaviour. Also remember that the value must be valid for the type, e.g. if you do

let mut x: MaybeUninit<bool> = MaybeUninit::uninit();
x.as_mut_ptr().cast::<u8>().write(0xFF);
let _ = x.assume_init();

then the last line is instant Undefined Behaviour, because 0xFF is not a valid value of type bool.

The types of left and right hand sides are different. You need to do

self.udp_frame[j as usize].as_mut_ptr().write(b);

Note that you could avoid calling the write method in this case and instead do

*self.udp_frame[j as usize].as_mut_ptr() = b;

This works because the type u32 has trivial destructor. However, it is dangerous to do in the general case, since the assignment operator calls Drop::drop on the left hand side if it has a nontrivial destructor. If the place on the left is uninitialized, this instantly causes Undefined Behaviour.

It is safer to always use write or read methods on the raw pointers.

1 Like

There is a lot to understand here. I got it to compile with a combination of fixes and suggested changes without apparently having to mess with MaybeUninit. It seems to run although I've not checked the outputs yet. I need to look through and understand what I've done then ask any questions on the unclear bits. Thanks for all the help.

Unfortunately it was rubbish. I've cleaned up the code so the error messages are clear and it's obvious something needs to be done to get the buffer into a form that Rust will accept. I found a thread about a year old that talks about adding a function to socket2 that takes a u8 array. I hope this is a solvable problem and not requiring a change to socket2. I've added the current code and error messages so hopefully someone can figure this out.

use std::thread;
use std::time::Duration;
use socket2;
use std::mem::MaybeUninit;
use std::sync::Arc;
use crate::protocol;

use crate::common::common_defs;
use crate::common::messages;

//==================================================================================
// Runtime object for thread
pub struct UDPRData<'a>{
    receiver : crossbeam_channel::Receiver<messages::ReaderMsg>,
	p_sock :  &'a socket2::Socket,
    p_addr :  &'a socket2::SockAddr,
    udp_frame : [MaybeUninit<u8>; common_defs::FRAME_SZ],
    prot_frame : [u8; common_defs::PROT_SZ*2],
    //pub i_cc: protocol::cc_in::CCDataMutex,
    pub i_seq: protocol::seq_in::SeqData,
    listen: bool,
}

// Implementation methods on UDPRData
impl UDPRData<'_> {
	// Create a new instance and initialise the default arrays
	pub fn new<'a>(receiver : crossbeam_channel::Receiver<messages::ReaderMsg>, p_sock : &'a socket2::Socket, p_addr : &'a socket2::SockAddr) -> UDPRData<'a> {
        // Create an instance of the cc_in type
        //let i_cc = protocol::cc_in::CCDataMutex::new();
        // Create an instance of the sequence type
        let i_seq = protocol::seq_in::SeqData::new();

		UDPRData {
            receiver: receiver,
			p_sock: p_sock,
            p_addr: p_addr,
            //udp_frame: unsafe {MaybeUninit::uninit().assume_init()},
            udp_frame: [MaybeUninit::uninit(); common_defs::FRAME_SZ],
            prot_frame: [0; common_defs::PROT_SZ*2],
            //i_cc: i_cc,
            i_seq: i_seq,
            listen: false,
		}
	}

    // Run loop for reader
    pub fn reader_run(&mut self) {
        loop {
            thread::sleep(Duration::from_millis(100));
            // Check for messages
            let r = self.receiver.try_recv();
            match r {
                Ok(msg) => {
                    match msg {
                        messages::ReaderMsg::Terminate => break,
                        messages::ReaderMsg::StartListening => {
                            self.listen = true;
                            println!("Listening for data...");
                        }
                    };
                },
                // Do nothing if there are no message matches
                _ => (),
            };
            // Check for read data
            if self.listen {
                let r = self.p_sock.recv_from(self.udp_frame.as_mut());
                match r {
                    Ok((sz,_addr)) => {
                        //println!("Received {:?} data bytes", sz);
                        
                        if sz == common_defs::FRAME_SZ {
                            self.split_frame();
                        } else {
                            println!("Received incomplete frame {}, discarding!", sz);
                        }
                    }
                    Err(_e) => (), //println!("Error or timeout on receive data [{}]", e),
                } 
            }
        }
    }

    // Split frame into protocol fields and data content and decode
    fn split_frame(&mut self) {  
        // Check for frame type
        if self.udp_frame[3] == common_defs::EP6 {
            // We have a frame of IQ data
            // First 8 bytes are the header, then 2x512 bytes of data
            // The sync and cc bytes are the start of each data frame
            //
            // Extract and check the sequence number
            //  2    1   1   4
            // Sync Cmd End Seq
            // if the sequence number check fails it means we have missed some frames
            // Nothing we can do so it just gets reported.
            let mut j: usize = 0;
            let mut ep6_seq : [u8; 4] = [0,0,0,0];

            for i in 4..8 {
                ep6_seq[j] = (self.udp_frame[i as usize]);
            }
            self.i_seq.check_ep6_seq(ep6_seq);

			// For 1,2 radios the entire dataframe is used
			// For 3 radios there are 4 padding bytes in each frame
            // TBD: For now fix num_rx at one as we don't have the data yet
            let num_rx = 1; 
            let mut end_frame_1 = common_defs::END_FRAME_1;
            let mut end_frame_2 = common_defs::END_FRAME_2;
            let mut data_sz = common_defs::PROT_SZ * 2;
            let mut num_smpls = common_defs::NUM_SMPLS_1_RADIO;
            if num_rx == 2 {
                num_smpls = common_defs::NUM_SMPLS_2_RADIO;
            } else if num_rx == 3 {
                num_smpls = common_defs::NUM_SMPLS_3_RADIO;
                end_frame_1 -= 4;
                end_frame_2 -= 4;
                data_sz -= 8;
            }

            // Extract the data from the UDP frame into the protocol frame
            j = 0;
            //unsafe {
                for b in common_defs::START_FRAME_1..end_frame_1 {
                    self.prot_frame[j] = self.udp_frame[b as usize];
                    j += 1;
                }
                j = 0;
                for b in common_defs::START_FRAME_2..end_frame_2 {
                    self.prot_frame[j] = self.udp_frame[b as usize];
                    j += 1;
                }
            //}

        } else if self.udp_frame[3] == common_defs::EP4 {
            // We have wideband data
            // TBD
        }
        protocol::decoder::frame_decode(126, 1, 48000, common_defs::FRAME_SZ*2, self.prot_frame);
    }

}

//==================================================================================
// Thread startup

pub fn reader_start(receiver : crossbeam_channel::Receiver<messages::ReaderMsg>, p_sock : Arc<socket2::Socket>, p_addr : Arc<socket2::SockAddr>) {
    thread::spawn(  move || {
        reader_run(receiver, &p_sock, &p_addr);
    });
}

fn reader_run(receiver : crossbeam_channel::Receiver<messages::ReaderMsg>, p_sock : &socket2::Socket, p_addr : &socket2::SockAddr) {
    println!("UDP Reader running");

    // Instantiate the runtime object
    let mut i_reader = UDPRData::new(receiver,  p_sock,  p_addr);

    // Exits when the reader loop exits
    i_reader.reader_run();

    println!("UDP Reader exiting");
    thread::sleep(Duration::from_millis(1000));
}

error[E0369]: binary operation == cannot be applied to type MaybeUninit<u8>
--> src\udp\udp_man\udp_reader.rs:114:30
|
114 | if self.udp_frame[3] == common_defs::EP6 {
| ----------------- ^^ ---------------- u8
| |
| MaybeUninit

error[E0308]: mismatched types
--> src\udp\udp_man\udp_reader.rs:128:30
|
128 | ep6_seq[j] = (self.udp_frame[i as usize]);
| ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected u8, found union MaybeUninit
| |
| expected due to the type of this binding
|
= note: expected type u8
found union MaybeUninit<u8>

error[E0308]: mismatched types
--> src\udp\udp_man\udp_reader.rs:153:42
|
153 | self.prot_frame[j] = self.udp_frame[b as usize];
| ------------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected u8, found union MaybeUninit
| |
| expected due to the type of this binding
|
= note: expected type u8
found union MaybeUninit<u8>

error[E0308]: mismatched types
--> src\udp\udp_man\udp_reader.rs:158:42
|
158 | self.prot_frame[j] = self.udp_frame[b as usize];
| ------------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected u8, found union MaybeUninit
| |
| expected due to the type of this binding
|
= note: expected type u8
found union MaybeUninit<u8>

error[E0369]: binary operation == cannot be applied to type MaybeUninit<u8>
--> src\udp\udp_man\udp_reader.rs:163:37
|
163 | } else if self.udp_frame[3] == common_defs::EP4 {
| ----------------- ^^ ---------------- u8
| |
| MaybeUninit

Well, if you're willing to pass a pre-initialised &mut [u8], then the Read implementation for Socket provides that for recv(). There doesn't seem to be a recv_from() equivalent, but it should be safe to just do what the source for Read::read() does, since the documentation of recv_from() states that it makes the same safety guarantees that make this sound.

2 Likes

You're just missing calls to assume_init() on the MaybeUninits in the array

Note: I did some find & replace's to get your code to compile without the other modules, so definitely don't just copy paste this into your actual project and expect it to work as-is

fn split_frame(&mut self) {
    unsafe {
        // Check for frame type
        if self.udp_frame[3].assume_init() == 0 {
            // We have a frame of IQ data
            // First 8 bytes are the header, then 2x512 bytes of data
            // The sync and cc bytes are the start of each data frame
            //
            // Extract and check the sequence number
            //  2    1   1   4
            // Sync Cmd End Seq
            // if the sequence number check fails it means we have missed some frames
            // Nothing we can do so it just gets reported.
            let mut j: usize = 0;
            let mut ep6_seq: [u8; 4] = [0, 0, 0, 0];

            for i in 4..8 {
                ep6_seq[j] = (self.udp_frame[i as usize]).assume_init();
            }
            // self.i_seq.check_ep6_seq(ep6_seq);

            // For 1,2 radios the entire dataframe is used
            // For 3 radios there are 4 padding bytes in each frame
            // TBD: For now fix num_rx at one as we don't have the data yet
            let num_rx = 1;
            let mut end_frame_1 = 0;
            let mut end_frame_2 = 0;
            let mut data_sz = 0 * 2;
            let mut num_smpls = 0;
            if num_rx == 2 {
                num_smpls = 0;
            } else if num_rx == 3 {
                num_smpls = 0;
                end_frame_1 -= 4;
                end_frame_2 -= 4;
                data_sz -= 8;
            }

            // Extract the data from the UDP frame into the protocol frame
            j = 0;
            //unsafe {
            for b in 0..end_frame_1 {
                self.prot_frame[j] = self.udp_frame[b as usize].assume_init();
                j += 1;
            }
            j = 0;
            for b in 0..end_frame_2 {
                self.prot_frame[j] = self.udp_frame[b as usize].assume_init();
                j += 1;
            }
            //}
        } else if self.udp_frame[3].assume_init() == 0 {
            // We have wideband data
            // TBD
        }
    }
}

That's assuming that all the parts of the array you're accessing have actually been initialized of course. Even if it isn't instant-UB to assume_init() in your case, you're still reading a basically random value if it hasn't been initialized. That probably isn't what you want (and could be a security risk depending on what you do with it)

I think you could also transmute the slice from &[MaybeUninit<u8>] to &[u8] and use the new slice instead of the field on self. That's making a stronger assumption that all of the elements in the array have been initialized though.

let udp_frame = unsafe { std::mem::transmute::<_, &[u8]>(self.udp_frame.as_slice()) };
// Check for frame type
if udp_frame[3] == 0 {
3 Likes

That's slice_assume_init_ref. See the source code for the transmute-avoiding pointer cast version (which one could just ape on stable).

4 Likes

Thanks, that makes sense now. I know some people don't like unsafe or transmute as they probably go against the philosophy but I guess unless Rust can takes hints from the underlying calls that they are safe it's the only way to make things work. I did have assume_init() initially but thought I could apply it to the whole array. I prefer the assume_init() route and its working now apart from some closedown issues I need to sort. Thanks very much for your time, it is appreciated.

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.