Rust: How to fix borrowed value does not live long enough

Viewed 2327

I have simple client/server application. I am receiving message on the server side from client but I want to send that response to the channel from server to other file and I am receiving error "borrowed value does not live long enough".

I have searched in the stack overflow for similar previous questions but not getting enough understanding of lifetime. Is there a good documentation or if simple example available on this topic?

For now if someone can help me to fix this code (may be edit the portion of code which needs to fix) that would be helpful.

Thanks in advance.

Server side:

use std::os::unix::net::UnixDatagram;
use std::path::Path;

fn unlink_socket (path: impl AsRef<Path>) {
    let path = path.as_ref();
    if Path::new(path).exists() {
        let result = std::fs::remove_file(path);
        match result {
            Err(e) => {
                println!("Couldn't remove the file: {:?}", e);
            },
            _ => {}
        }
    }
}

pub fn tcp_datagram_server() {
    pub static FILE_PATH: &'static str = "/tmp/datagram.sock";
    let (tx, rx) = mpsc::channel();
    let mut buf = vec![0; 1024];
    unlink_socket(FILE_PATH);
    let socket = match UnixDatagram::bind(FILE_PATH) {
        Ok(socket) => socket,
        Err(e) => {
            println!("Couldn't bind: {:?}", e);
            return;
        }
    };
    println!("Waiting for client to connect...");
    loop {
        let received_bytes = socket.recv(buf.as_mut_slice()).expect("recv function failed");
        println!("Received {:?}", received_bytes);
        let received_message = from_utf8(buf.as_slice()).expect("utf-8 convert failed");
        tx.clone().send(received_message);
    }
}

fn main() {
   tcp_datagram_server();
}

client side:

use std::sync::mpsc;
use std::os::unix::net::UnixDatagram;
use std::path::Path;
use std::io::prelude::*;

pub fn tcp_datagram_client() {
    pub static FILE_PATH: &'static str = "/tmp/datagram.sock";
    let socket = UnixDatagram::unbound().unwrap();
    match socket.connect(FILE_PATH) {
        Ok(socket) => socket,
        Err(e) => {
            println!("Couldn't connect: {:?}", e);
            return;
        }
    };
    println!("TCP client Connected to TCP Server {:?}", socket);
    loop {
        socket.send(b"Hello from client to server").expect("recv function failed");
    }
}

fn main() {
   tcp_datagram_client();
}

Error I am getting

error[E0597]: `buf` does not live long enough
  --> src/unix_datagram_server.rs:38:42
   |
38 |         let received_message = from_utf8(buf.as_slice()).expect("utf-8 convert failed");
   |                                          ^^^ borrowed value does not live long enough
...
41 | }
   | -
   | |
   | `buf` dropped here while still borrowed
   | borrow might be used here, when `tx` is dropped and runs the `Drop` code for type `std::sync::mpsc::Sender`
   |
   = note: values in a scope are dropped in the opposite order they are defined

error: aborting due to previous error; 8 warnings emitted
2 Answers

For now if someone can help me to fix this code (may be edit the portion of code which needs to fix) that would be helpful.

Well the message seems rather clear. send does exactly what it says it does, it sends the parameter through the channel. This means the data must live long enough and remain valid "forever" (it needs to be alive and valid in the channel, as well as when fetched from it by the receiver).

That is not the case here. rustc can't understand that the function never returns, and it can panic anyway which will end up the same: the function will terminate, which will invalidate buf. Since received_message borrows buf, that means received_message can't be valid after the function has terminated. But at that point the message would still be in the channel waiting to be read (or retrieved by the receiver doing who knows what).

Therefore your construction is not allowed.

A second issue is that you're overwriting the buffer data on every loop, which has the same effect of breaking the message you sent during the previous iteration, and thus is not correct either. Though Rust won't let you do that either: if you work around the first error it will tell you that there's an outstanding shared borrow (the message sent through the channel) so you can't modify the backing buffer in the following iteration.

The solution is quite simple: have each iteration create an owned string (copying the current iteration's message) and send that through the channel:

tx.clone().send(received_message.to_string());

Also, these are more style / inefficiency remarks but:

  • The clone() on tx is completely redundant. The point of having a sender that is Clone is being able to send from multiple threads (hence mp in the channel name, that's for multiple producers). Here you have a single thread, the original sender works fine.

  • .as_slice() and .as_mut_slice() are rarely used unless necessary, which they aren't here: array references coerce to slices, so you can just use &mut buf and &buf. And why are you calling Path::new on something that's already a path? It doesn't do anything but it's not useful either.

  • It is rather annoying that your snippet is missing multiple imports and thus doesn't even compile as is.

  • From more of a unixy perspective, errors are usually printed on stderr. In Rust, eprintln does that for you (otherwise working in the same way println does). And I don't understand the purpose of marking a lexically nested static pub. Since the static is inside the function it's not even visible to the function's siblings, to say nothing of external callers. As a result I'd end up with this:

    use std::os::unix::net::UnixDatagram;
    use std::path::Path;
    use std::sync::mpsc;
    use std::str::from_utf8;
    
    fn unlink_socket (path: impl AsRef<Path>) {
        let path = path.as_ref();
        if path.exists() {
            if let Err(e) = std::fs::remove_file(path) {
                eprintln!("Couldn't remove the file: {:?}", e);
            }
        }
    }
    
    static FILE_PATH: &'static str = "/tmp/datagram.sock";
    pub fn tcp_datagram_server() {
        unlink_socket(FILE_PATH);
        let socket = match UnixDatagram::bind(FILE_PATH) {
            Ok(socket) => socket,
            Err(e) => {
                eprintln!("Couldn't bind: {:?}", e);
                return;
            }
        };
    
        let (tx, _) = mpsc::channel();
        let mut buf = vec![0; 1024];
        println!("Waiting for client to connect...");
        loop {
            let received_bytes = socket.recv(&mut buf).expect("recv function failed");
            println!("Received {:?}", received_bytes);
            let received_message = from_utf8(&buf).expect("utf-8 convert failed");
            tx.send(received_message.to_string());
        }
    }
    

There's a hint in the compiler message, that values in a scope are dropped in the opposite order they are defined in, and in the example, buf is defined after tx, which means it will be dropped before tx. Since a reference to buf (in the form of received_message) is passed to tx.send(), then buf should live longer that tx, and therefore switching the definition order will fix this particular error (ie. switch lines 19 and 20).

Related