Search code examples
rust

How to create an object with references to variables owned by constructor?


Here is a tentative socket constructor:

use std::net::TcpStream;

use rustls::{ClientConnection, RootCertStore, Stream};

pub struct TlsSocket<'a> {
    tls_instance: Stream<'a, ClientConnection, TcpStream>,
}

impl<'a> TlsSocket<'a> {
    pub fn new() -> Self {
        let root_store = RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
        let mut config = rustls::ClientConfig::builder()
            .with_root_certificates(root_store)
            .with_no_client_auth();

        // Allow using SSLKEYLOGFILE.
        config.key_log = Arc::new(rustls::KeyLogFile::new());

        let server_name = "my_site.net".try_into().unwrap();
        let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name).unwrap();
        let mut sock = TcpStream::connect("my_site.net:8000").unwrap();
        let tls = rustls::Stream::new(&mut conn, &mut sock);

        TlsSocket { tls_instance: tls }
    }
}

However, I get the error:

cannot return value referencing local variable `sock`
returns a value referencing data owned by the current function

I think I understand the error: at the end of the function new, conn and sock cannot be dangling. new should move them out, somehow.

I thought the lifetime annotation on Stream would let the compiler know that conn and sock must exist as long as the tls rustls::Stream exists...

pub struct Stream<'a, C, T>
where
    C: 'a + ?Sized,
    T: 'a + Read + Write + ?Sized, {
    pub conn: &mut C,
    pub sock: &mut T,
}

But that's not the case.

What I am doing wrong?


Solution

  • I thought the lifetime annotation on Stream would let the compiler know that conn and sock must exist as long as the tls rustls::Stream exists...

    You are correct -- it does convey exactly that information to the compiler. However, the compiler will not automatically make it so! You are responsible for putting them somewhere that will outlive the Stream, and also where they won't be moved until the Stream ceases to exist. Because you haven't, the compiler gives you this error.

    In other words, lifetimes give the compiler enough information to verify that what you're doing is correct, but it won't magically do extra things to satisfy the lifetime constraints.

    Stream refers to an existing connection and socket, without taking ownership of them. Consider using StreamOwned instead, which takes ownership of these values and therefore does not need to borrow anything.