Search code examples
rust

How to avoid cloning `self` when calling multiple struct method in rust?


i was trying to call two struct methods, namely self.bootstrap() and self.run(), inside self.main(). However, i got a compiler warning for the self.run() , said use of moved value: self, value used here after move

i believe the issue is because self.bootstrap() has taken ownership of self, and not returning it. thus, main no longer has the ownership of self, causing the ownership issue of self.run()

the FailedServer struct and implementation is a minimal reproducible example of this error

mod minimal_example {
    use std::{net::TcpStream, sync::Arc};

    struct FailedServer {
        nodes: Vec<String>,
    }

    impl FailedServer {
        pub fn main(self: Arc<Self>) -> Result<(), Box<dyn std::error::Error>> {
            self.bootstrap();

            // the below `self.run()` with this compile error
            // use of moved value: `self`
            // value used here after move
            self.run() // <- compiler error
        }

        fn bootstrap(self: Arc<Self>) {
            let nodes = self.nodes.clone();
            let len = nodes.len();
            for i in 0..len {
                let node = nodes[i].clone();
                std::thread::spawn(move || {
                    TcpStream::connect(node);
                });
            }
        }

        fn run(self: Arc<Self>) -> Result<(), Box<dyn std::error::Error>> {
            loop {
                // a blocking loop to do something
            }

            Ok(())
        }
    }
}

i have tried to clone self inside main before calling self.bootstrap() in the example below and it works. But it seems weird to me to do as it would imply that if i have to call 10 different struct methods inside main, i would have to clone self multiple times to keep the ownership inside main.

so my question is, is it actually the right way to do or it is my skill issue.

below is the working example

mod minimal_example {
    use std::{net::TcpStream, sync::Arc};

    struct WorkableServer {
        nodes: Vec<String>,
    }

    impl WorkableServer {
        pub fn start(self: Arc<Self>) -> Result<(), Box<dyn std::error::Error>> {
            self.clone().bootstrap();

            self.run()
        }

        fn bootstrap(self: Arc<Self>) {
            let nodes = self.nodes.clone();
            let len = nodes.len();
            for i in 0..len {
                let node = nodes[i].clone();
                println!("Bootstrapping to node: {}", node);
                std::thread::spawn(move || {
                    TcpStream::connect(node);
                });
            }
        }

        fn run(self: Arc<Self>) -> Result<(), Box<dyn std::error::Error>> {
            loop {
                // a blocking loop to do something
            }

            Ok(())
        }
    }
}

Solution

  • just use &mut self instead of self, since it will not take ownership of self.

    mod mutable_reference {
        struct Server;
        impl Server {
            pub fn start(mut self) {
                self.startup();
                self.config();
                self.bootstrap();
                self.init();
                self.run()
            }
            pub fn startup(&mut self) {
                println!("starting up . . .");
            }
            pub fn config(&mut self) {
                println!("reading config . . .");
            }
            pub fn bootstrap(&mut self) {
                println!("boostraping . . .");
            }
            pub fn init(&mut self) {
                println!("initalizing . . .");
            }
            pub fn run(self) {
                println!("running . . .");
                loop { /* do re mi */ }
            }
        }
    }
    

    but if you want the function to take ownership, return Self( or Arc<Self> in your case), in the function to chain different function together.

    mod take_ownership {
        struct Server;
        impl Server {
            pub fn start(self) {
                self
                    .startup()
                    .config()
                    .bootstrap()
                    .init()
                    .run()
            }
            pub fn startup(self) -> Self {
                println!("starting up . . .");
                self
            }
            pub fn config(self) -> Self {
                println!("reading config . . .");
                self
            }
            pub fn bootstrap(self) -> Self {
                println!("boostraping . . .");
                self
            }
            pub fn init(self) -> Self {
                println!("initalizing . . .");
                self
            }
            pub fn run(self) {
                println!("running . . .");
                loop { /* do re mi */ }
            }
        }
    }