Static, structs, OO best use of

Hi,
Before I lock down what I've done so far I would like to know if what I've done is the best way.
I have 4 modules. Two of these are just functions and two use an OO pattern. I believe that the level of encapsulation is the module rather than a struct or impl as it would be in an OO language.

So for example I have:

pub struct Sockdata{
        sock2 : Arc<socket2::Socket>,
    }
    
    impl Sockdata {
        pub fn new() -> Sockdata {
            let sock = Self::udp_open_bc_socket();
            Sockdata {  
                sock2 : Arc::new(socket2::Socket::from (sock)),
            }
        }
...

so I create an instance of this and the methods that have the hidden parameter (&mut self) are called by instance.method(). Now I guess I could have just done.

static sock2 : Arc<socket2::Socket> = What do I put here to initialise ?Option<None>?;

pub fn init() {
    let sock = Self::udp_open_bc_socket();
    let sock2 = Arc::new(socket2::Socket::from (sock));
}

or

pub struct Sockdata{
        sock2 : Arc<socket2::Socket>,
    }

pub fn init() {
    let sock = udp_open_bc_socket();  
    let Sockdata::sock2 = Arc::new(socket2::Socket::from (sock));
 }

The first above I don't know how to initialise the static and in the second it can't seem to see sock2 telling me its not in scope.

It's pretty weird to put sockets into a global/static. Are you trying to create a singleton? Creating a singleton is very rarely the right solution. If you possibly can, just create local variables.

Anyway, if you want to create a mutable static/global, you have to guard it against mutable aliasing. One simple way to do that is by putting into an once_cell::sync::Lazy.

Because sock2 is a local variable. The let statement declares a local variable, and it always creates a new binding. If you want to assign to an existing binding, don't use let.

2 Likes

For the second non-working example the fix would be something like this, similarly to how you wrote it in the new method:

let sock = udp_open_bc_socket();  
let sock_data = Sockdata {  
    sock2 : Arc::new(socket2::Socket::from (sock)),
};

Note that you would need to return the instance for the init function to be useful.

For the first non-working example, initializing complex statics in Rust is relatively complex syntactically compared to other languages. The done thing is to use lazy_static or once_cell.

Note that something based on once_cell is expected to be added to the std library eventually.

1 Like

Thanks. So that's fixed up the second example. I thought static was a bad idea so won't be using those. I have the choice then of the OO pattern or the second example. As these should be singletons probably the second example is the best choice and use the OO pattern only if I need multiple instances. Is that the best approach?

I don't get what exactly the two patterns are and what the point of them is (especially the "OO" one)? (What I think is) your second example now contains a syntax error after the edit (Rust structs don't have static/global members, fields are always in instances of the type), and it's also an apparent attempt at creating a singleton.

I fixed up the example 2 code thanks to Ryan.

pub struct Sockdata{
        sock2 : Arc<socket2::Socket>,
    }

    pub fn init() {
        let sock = udp_open_bc_socket();  
        let sock_data = Sockdata { 
            sock2 : Arc::new(socket2::Socket::from (sock)),
        };
    }
    
    fn udp_open_bc_socket() -> UdpSocket {
        let sock = UdpSocket::bind(get_ip() + ":" + "10000").expect("couldn't bind to address");
        sock.set_broadcast(true).expect("set_broadcast call failed");
        sock.set_read_timeout(Some(Duration::from_millis(10))).expect("set_read_timeout call failed");
        return sock
    }

    fn get_ip() -> String{
        let iface = get_if_addrs::get_if_addrs().unwrap();
        return iface[0].ip().to_string();
    }

It seems to be quite a subtle difference. Essentially the OO pattern has an impl block and an fn new() that creates an instance and initialises the struct. The procedural pattern has the same struct and a function that initialises it. If you add a self parameter to function in the impl block they become methods that can be called by instance.function(). Its all a bit strange to me although I've used procedural, OO and functional languages Rust is just hard to categorise having elements of all three but not as I know them.

After playing with the second sample for some time it was way short of working and seems really difficult and a not very nice structure so I will stick with the OO type pattern when I need instance data.

This still doesn't work because init() just creates an instance of Sockdata and then discards it. It would need to either return it or store it in some global variable (which requires some form of synchronisation). Returning it would make init() functionally the same as new() from the first example, except that it wouldn't be in the Sockdata namespace.

2 Likes

I don't know what background you have exactly, but perhaps you would benefit from some comparisons between OO languages and Rust.

There is no implicit link between the fields of a data type and methods you implement on it, for example, where a type's fields may appear like unattached variables ("instance variables"). Instead, methods with access to the fields take self or &self or &mut self (et cetera) as an argument, and the fields are explicitly accessed through self (self.field).

Non-receiver (no self parameter) associated functions act pretty much like stand-alone functions, outside of any type / class / whatever. There is no implied instance; there is no owned data for these functions to access.

Types can have associated consts, which are the closest things to "class variables". But you can't modify them. You can't have associated statics.

A short example.

4 Likes

Thanks for the help all. Yes I realised it was incomplete and in my trying to fix it it was getting complicated which is why I reverted to the OO type pattern. I have much to learn on Rust I admit and I think it will be quite a long process, just one week in so far. Traits are probably next on the list.