-1

I m having the following pseudocode inside a crate

pub static struct connection {
  pub Id: i32,
  pub speed: i32,
}

impl Default for connection {
  fn default() -> Self {
      connection {
          Id: open_connection(),
          Speed: get_speed_at_init(),
      }
  }
}

That crate is imported into an other project and run into several worker threads where each threads use the imported crate in an infinite loop.

Now will I be able to use self.connection.Id to perform network operations on it, or will open_connection() be evaluated several times resulting in opening too many connections instead of the expected just one time?

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
user2284570
  • 2,891
  • 3
  • 26
  • 74
  • The `Default` trait is not magic. It declares a single function which your types can implement and then once it's called it works like any other function. So yes, if the function is called multiple times then multiple connections would be made in your case. – pretzelhammer Dec 12 '20 at 23:09
  • @pretzelhammer oh sorry. I did a mistake in my code above please see the edit. – user2284570 Dec 12 '20 at 23:14
  • Does this answer your question? [How can you make a safe static singleton in Rust?](https://stackoverflow.com/q/27221504/2766908) – pretzelhammer Dec 12 '20 at 23:28
  • @pretzelhammer I don t see how that link is related to my question at all. Because of the underlying network protocol, no thread can pick the same network message at the same time. It s hapenning without any kind of synchronisation (no such things li'e sémaphores or monitors or mutex). **I just want all threads using the low level crate to share the same network connection** which is not closed during the lifetime of the process. – user2284570 Dec 12 '20 at 23:36
  • 1
    How can multiple threads share the same network connection safely without any synchronization? What is the type of this network connection? Can you please add way more details and context to your question because right now it's not clear what you're asking. – pretzelhammer Dec 12 '20 at 23:47
  • @pretzelhammer messages are small enough to fit on udp and on the other hand, the protocol is cpu hungry and the number of threads small (no more than 3). **the question doesn t talk about synchronization at all as only you are briging it**. It s just about whether having threads or simply loops impacts the values of the structure. – user2284570 Dec 12 '20 at 23:56
  • Please update your question by adding a [Minimal Complete Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example). – pretzelhammer Dec 13 '20 at 00:00
  • 1
    I'm voting to close this question as "needs details" because discussion at [your other question](https://stackoverflow.com/q/65292878/3650362) suggests you actually mean to ask something about `static`s that is not evidenced in the question itself. Providing a [mre], and asking a concrete, answerable question about its behavior ("Does function `f` get called once or twice in this code?") is a good idea. – trent Dec 14 '20 at 19:19

2 Answers2

1

Several times. This can be trivially demonstrated:

struct Struct;

impl Default for Struct {
    fn default() -> Self {
        println!("default called");
        Struct
    }
}

fn main() {
    let s1 = Struct::default(); // prints "default called"
    let s2 = Struct::default(); // prints "default called"
}

playground

There's nothing special about the Default trait or the default function. They work like any other trait and any other function.

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
  • Default() is never called directly. Its only about reading its members variables. I wanted to have constants defined once at run time, and no function in the sub crate can be run only one time. Is there an other way to do what I want in a similar way? – user2284570 Dec 12 '20 at 23:22
1

If I understand your question correctly, you want a global mutable variable of type Connection and you are wondering if it will be reinitialized every time the variable is imported from a different crate/thread.

In this case, you do not need the default trait. Default is not a special trait. It is only in the standard library because it is very common. To define a global variable, you must initialize the value directly. You must first define the type Connection:

pub struct Connection {
  pub id: i32,
  pub speed: i32,
}

And then create the global variable. There are many ways to create a global mutable variable as explained in this answer. Assuming that Connection stores things other than integers you will probably need to wrap the global in an Arc for thread-safety, and a Mutex if you require mutability across threads. You can use lazy_static to initialize the variable at runtime which allows you to make the necessary method calls to create the Connection:

lazy_static! {
    pub static ref conn: Arc<Connection> = Arc::new(Connection {
        id: open_connection(),
        speed: get_speed_at_init(),
    });
}

Now to answer your question.

No, spawning a new thread or importing a variable from a crate will not re-initialize the static variable. A static variable represents a precise memory location in the program. All references to the static refer to the same memory location, whether they live in the same module, crate, or thread. We can test this by generating a random id in open_connection and placing the global conn in a seperate module:

pub mod connection {
    lazy_static! {
        pub static ref conn: Arc<Connection> = Arc::new(Connection {
            id: open_connection(),
            speed: get_speed_at_init(),
        });
    }

    fn open_connection() -> i32 {
        let mut rng = rand::thread_rng();
        rng.gen()
    }
}

You can access the conn from multiple modules or crates:

mod a {
    use crate::connection::conn;
    pub fn do_stuff() {
        println!("id from a: {}", conn.id);
    }
}

// a different crate
mod b {
    use crate::connection::conn;
    pub fn do_stuff() {
        println!("id from b: {}", conn.id);
    }
}

And multiple threads:

mod c {
    use crate::connection::conn;
    pub fn do_stuff() {
        for i in 0..5 {
            std::thread::spawn(move || {
                println!("id from thread #{}: {}", i, conn.id);
            })
            .join()
            .unwrap();
        }
    }
}

But the id will always refer to the id that was generated when the global variable was initially declared:

fn main() {
    a::do_stuff();
    different_crate::b::do_stuff();
    c::do_stuff();
}

// id from a: 1037769551
// id from b: 1037769551
// id from thread #0: 1037769551
// id from thread #1: 1037769551
// id from thread #2: 1037769551
// id from thread #3: 1037769551
// id from thread #4: 1037769551

Playground link: Unfortunately you cannot have multiple crates on the playground, so modules were the best I could do. If you are still unsure, you could always test it out locally.

Ibraheem Ahmed
  • 11,652
  • 2
  • 48
  • 54
  • A mutex is not required and adds syscall overhead where the aim is to race against a competitor. So how to do this without using a Mutex? – user2284570 Dec 14 '20 at 20:05
  • @user2284570 Sorry, I assumed that you wanted mutability. I updated my answer to remove the `Mutex`. – Ibraheem Ahmed Dec 14 '20 at 20:17
  • @user2284570 If `Connection` is as simple as the example you provided, you can omit the `Arc` as well because integers are already `Sync` – Ibraheem Ahmed Dec 14 '20 at 20:26