Paul Thompson
Paul Thompson

Reputation: 3330

How are you able to create partially initialised structs?

When creating a struct in Rust it seems like it's difficult to create one without having all of the fields set. For example with the following code

struct Connection {
    url: String,
    stream: TcpStream
}

You aren't able to set url without giving stream as well.

// Compilation error asking for 'stream'
let m = Connection { url: "www.google.com".to_string() }; 

How are you able to create these references that might be Option<None> until a later time?

The best I have found is using the Default trait, but I'd rather not have to create the TcpStream until a later time than when the struct is initialised. Am I able to do this with something like a Box?

Upvotes: 24

Views: 20802

Answers (3)

Shepmaster
Shepmaster

Reputation: 430681

As an extension to Jorge Israel Peña's answer, you can use a builder. The builder has all the optional fields and produces the final value without Options:

use std::net::TcpStream;

struct ConnectionBuilder {
    url: String,
    stream: Option<TcpStream>,
}

impl ConnectionBuilder {
    fn new(url: impl Into<String>) -> Self {
        Self {
            url: url.into(),
            stream: None,
        }
    }

    fn stream(mut self, stream: TcpStream) -> Self {
        self.stream = Some(stream);
        self
    }

    fn build(self) -> Connection {
        let url = self.url;
        let stream = self
            .stream
            .expect("Perform actual error handling or default value");
        Connection { url, stream }
    }
}

struct Connection {
    url: String,
    stream: TcpStream,
}

impl Connection {
    fn method_that_uses_stream(&self) {
        // can use self.stream here
    }
}

This means that you don't have to litter your code with checks to see if the stream has been set yet.

See also:

Upvotes: 7

tafia
tafia

Reputation: 1562

All fields indeed have to be initialized when creating the struct instance (there is no null in Rust) so all the memory is allocated. There is often a dedicated method (like new) that sets default values for fields which are supposed to be modified at a later stage.

I'd use the Box when you don't know the size of the field (like Vec does).

Upvotes: 4

Jorge Israel Pe&#241;a
Jorge Israel Pe&#241;a

Reputation: 38586

One thing you can do is to wrap the TcpStream in an Option, i.e. Option<TcpStream>. When you first construct the struct, it'll be None, and when you initialize it you make it self.stream = Some(<initialize tcp stream>). Wherever you use the TCPStream, you'll have to check if it's Some, i.e. if it has already been initialized. If you can guarantee your behavior then you can just unwrap(), but it's probably better to make a check anyways.

struct Connection {
    url: String,
    stream: Option<TcpStream>
}

impl Connection {
    pub fn new() -> Connection {
        Connection {
            url: "www.google.com".to_string(),
            stream: None,
        }
    }

    pub fn initialize_stream(&mut self) {
        self.stream = Some(TcpStream::connect("127.0.0.1:34254").unwrap());
    }

    pub fn method_that_uses_stream(&self) {
        if let Some(ref stream) = self.stream {
            // can use the stream here
        } else {
            println!("the stream hasn't been initialized yet");
        }
    }
}

This is similar to what is done in Swift, in case you're familiar with that language.

Upvotes: 17

Related Questions