Stuck with lifetime! again

Hello everyone,

whenever i think i finally got how lifetimes work, i find myself in a situtation fighting with the compiler with lifetimes :hot_face: :hot_face: :hot_face:

#[tokio::main]
async fn main() -> io::Result<()> {
    let mut listener = TcpListener::bind("127.0.0.1:2030").await?;
    let users: Vec<User> = Vec::new();

    loop {
        let (mut stream, addr) = listener.accept().await?;

        tokio::spawn(async move {
            let (reader, writer) = stream.split();
            let user = User::new(reader, writer, addr);

            user.ask("What's your name?").await;
        });
    }
}

#[derive(Debug)]
pub struct User<T> {
    pub reader: ReadHalf<T>,
    pub writer: WriteHalf<T>,
    pub addr: SocketAddr,
    pub username: String,
}

impl<T> User<T>
where T: AsyncRead + AsyncWrite {
    pub async fn new(reader: ReadHalf<'_>, writer: WriteHalf<'_>, addr: SocketAddr) -> Self {
        Self {
            reader,
            writer,
            addr,
            username: String::new(),
        }
    }

    pub async fn ask(&mut self, quest: &str) -> io::Result<()> {
        let quest = format!(" >>> {} ", quest);
        self.writer.write_all(quest.as_str().as_bytes()).await?;

        Ok(())
    }
}

anyone can help me please (with some explanation) on how can i send reader and writer to User::new , and store them as fields in struct for later use?

Are you using TcpStream::split or tokio::io::split? And what is the error?

yes i am ysing tcpstream::split
the error

warning: unused import: `tokio::io::AsyncBufReadExt`                                                                                                                                                          [21/21]
 --> src/user.rs:7:5
  |
7 | use tokio::io::AsyncBufReadExt;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: unused import: `tokio::net::TcpStream`
 --> src/user.rs:8:5
  |
8 | use tokio::net::TcpStream;
  |     ^^^^^^^^^^^^^^^^^^^^^

warning: unused import: `tokio::io::BufReader`
  --> src/user.rs:11:5
   |
11 | use tokio::io::BufReader;
   |     ^^^^^^^^^^^^^^^^^^^^

error[E0106]: missing lifetime specifier
  --> src/user.rs:15:17
   |
15 |     pub reader: ReadHalf<T>,
   |                 ^^^^^^^^^^^ expected lifetime parameter

error[E0106]: missing lifetime specifier
  --> src/user.rs:16:17
   |
16 |     pub writer: WriteHalf<T>,
   |                 ^^^^^^^^^^^^ expected lifetime parameter

error[E0107]: wrong number of type arguments: expected 0, found 1
  --> src/user.rs:15:26
   |
15 |     pub reader: ReadHalf<T>,
   |                          ^ unexpected type argument

error[E0107]: wrong number of type arguments: expected 0, found 1
  --> src/user.rs:16:27
   |
16 |     pub writer: WriteHalf<T>,
   |                           ^ unexpected type argument

error: cannot infer an appropriate lifetime
  --> src/user.rs:22:22
   |
22 |     pub async fn new(reader: ReadHalf<'_>, writer: WriteHalf<'_>, addr: SocketAddr) -> Self {
   |                      ^^^^^^ ...but this borrow...
23 |         Self {
24 |             reader,
   |             ------ this return type evaluates to the `'static` lifetime...
   |
note: ...can't outlive the lifetime `'_` as defined on the method body at 22:39
  --> src/user.rs:22:39
   |
22 |     pub async fn new(reader: ReadHalf<'_>, writer: WriteHalf<'_>, addr: SocketAddr) -> Self {
   |                                       ^^

error: cannot infer an appropriate lifetime
  --> src/user.rs:22:44
   |
22 |     pub async fn new(reader: ReadHalf<'_>, writer: WriteHalf<'_>, addr: SocketAddr) -> Self {
   |                                            ^^^^^^ ...but this borrow...
...
25 |             writer,
   |             ------ this return type evaluates to the `'static` lifetime...
   |
note: ...can't outlive the lifetime `'_` as defined on the method body at 22:39
  --> src/user.rs:22:39
   |
22 |     pub async fn new(reader: ReadHalf<'_>, writer: WriteHalf<'_>, addr: SocketAddr) -> Self {
   |                                       ^^

error: aborting due to 6 previous errors

Some errors have detailed explanations: E0106, E0107.
For more information about an error, try `rustc --explain E0106`.
error: could not compile `mixed`.

To learn more, run the command again with --verbose.

That halves returned by that split function are more or less just references into the corresponding stream, which means that if you put them in a struct, that struct now contains a reference to the tcp stream, and needs lifetimes for that reason. It looks like this:

#[derive(Debug)]
pub struct User<'a> {
    pub reader: ReadHalf<'a>,
    pub writer: WriteHalf<'a>,
    pub addr: SocketAddr,
    pub username: String,
}
impl<'a> User<'a> {
    pub fn new(reader: ReadHalf<'a>, writer: WriteHalf<'a>, addr: SocketAddr) -> Self {
        ...
    }
}

Note that the halves are specific to TcpStream, so they are not generic over the type of stream. Additionally you should be aware that the halves being references all of the consequences of references, e.g. you can't send them to separate tasks, and the tcp stream must be in scope for the full duration of your User struct (e.g. you can't put it in a vector where the tcp stream is not in scope).

Unless the above works in your situation, you should probably consider not splitting the stream and just keeping the TcpStream object around.

2 Likes

i tried to use 'a as suggested above but received another error

error[E0308]: mismatched types
  --> src/user.rs:23:9
   |
23 | /         Self {
24 | |             reader,
25 | |             writer,
26 | |             addr,
27 | |             username: String::new(),
28 | |         }
   | |_________^ lifetime mismatch
   |
   = note: expected struct `user::User<'static>`
              found struct `user::User<'a>`
note: the lifetime `'a` as defined on the impl at 21:6...
  --> src/user.rs:21:6
   |
21 | impl<'a> User<'a> {
   |      ^^
   = note: ...does not necessarily outlive the static lifetime

User::new() doesn't need to be async, since it's not doing any I/O. With that taken care of, the rest isn't too difficult. Here's a playground which reads the name and prints it out.

1 Like

@inejge assume i am calling .await inside fn new which will require new to be async in this case what is the rule of the lifetime?

In this case, you could return User<'a> instead of Self in an async fn new. (Then, you'd additionally have to call .await after new().) The problem with Self is that you can't parametrize it either with a type or a lifetime, and the return type of an async fn needs to capture all input lifetimes. Generally, this part of the language is still rather brittle and unergonomic.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.