Lifetime parameters

I've read everything I can find but I just don't understand where these things are supposed to go or maybe I just made a mistake and don't need them at all. The fn reader_start() at the end kicks it all off.

//==================================================================================
// Runtime object for thread
pub struct UDPRData{
    receiver : crossbeam_channel::Receiver<messages::ReaderMsg>,
	p_sock :  & socket2::Socket,
    p_addr :  & 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(receiver : crossbeam_channel::Receiver<messages::ReaderMsg>, p_sock : & socket2::Socket, p_addr : & socket2::SockAddr) -> UDPRData {
        // 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()},
            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(&mut self.udp_frame);
                match r {
                    Ok((_sz,_addr)) => {
                        //println!("Received {:?} data bytes", sz);
                        self.split_frame();
                    }
                    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) {
        // TBD
        protocol::decoder::frame_decode(126, 1, 48000, common_defs::FRAME_SZ*2, self.udp_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[E0106]: missing lifetime specifier
--> src\udp\udp_man\udp_reader.rs:42:12
|
42 | p_sock : & socket2::Socket,
| ^ expected named lifetime parameter
|
help: consider introducing a named lifetime parameter
|
40 ~ pub struct UDPRData<'a>{
41 | receiver : crossbeam_channel::Receivermessages::ReaderMsg,
42 ~ p_sock : &'a socket2::Socket,
|

error[E0106]: missing lifetime specifier
--> src\udp\udp_man\udp_reader.rs:43:15
|
43 | p_addr : & socket2::SockAddr,
| ^ expected named lifetime parameter

It looks like one of your UDPRData struct's fields is a reference and the compiler has suggested introducing a lifetime ('a) so we have a name that can be used to refer to how long the reference is alive for.

Try the suggestion mentioned in the compiler error and see if that fixes your problem.

1 Like

I had two further errors after that and ended up with this. It's really hard to understand how this helps the compiler. I would have thought it all needs to live until i_reader goes out of scope which it will do when the thread exits. What exactly is the compiler worried about here.

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();

How should the compiler know which one of the (potentially different) lifetimes of p_sock and p_addr should it tie the lifetime of the return value to, if not via the lifetime annotations? It's not like all functions are constructors... you might want to take 2 reference-typed arguments, and only put 1 of them into the return value, while only temporarily using the other one, for example.

You can read more about lifetime elision rules here.

I can read the rules and understand the what but the why is more difficult to understand. The statement 'In order to make common patterns more ergonomic' implies it somehow makes the code more efficient but is not related to preventing dangling pointers. Perhaps if you could explain what might happen if the compiler did not require this (in my particular case) I might better grasp its significance.

It doesn't; lifetime annotations don't affect runtime behavior in any way at all. All lifetime elision does is it makes code less verbose to the human reader in the case of methods (with a &self or &mut self argument) and functions which only have a single reference-type argument, by tying the output lifetime to the lifetime of self and the single reference argument, respectively, so you can write this:

fn slice(string: &str, end: usize) -> &str {
}

impl Map {
    fn get(&self, key: &Key) -> &Value {
    }
}

instead of this:

fn slice<'a>(string: &'a str, end: usize) -> &'a str {
}

impl Map {
    fn get<'map, 'key>(&'map self, key: &'key Key) -> &'map Value {
    }
}

Indeed, lifetime elision has very little to do with dangling pointers (only insofar as elision rules won't lead to code that accidentally creates dangling pointers, modulo potential compiler bugs).

If the compiler did not require this, then there would be no way to decide what the output lifetime should be here:

fn pick_first(first: &str, second: &str) -> &str {
    first
}

In the function above, the signature doesn't say which of the arguments should generate the lifetime for the return type. The compiler could pick one arbitrarily, but 1. that would be very surprising in a very bad way, and 2. if it picked the wrong one, e.g. it used the lifetime of second, then there would be no way to have the function compile if you wanted to return the other reference.

1 Like

Thanks for the explanations. I can see what you are getting at. I think what was confusing was that the function new() returns the whole structure so there is no issue about which value the return type should be bound to, its bound to all of them which is why I said in my particular case.

But the compiler doesn't know that. It intentionally doesn't look at the implementation of a function to determine its interface. The interface is a contract that should be very explicitly specified for clarity and for avoiding accidental changes. It should not be influenced by the exact implementation, because then the exact implementation could lead to unintended breakage of the interface.

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.