Question about the static function

I have some questions about the static function in Rust. Here is some sample code. I will show the question as concisely as possible.

My idea is this: I receive some data from MQTT, then send it to another worker thread through an mpsc pipeline for parsing the data.

So, I used the AsyncClient in the paho-mqtt crate and defined a new object to manage the pipeline and the MQTT client:

pub struct AsyncMqttClient<T>
where
    T: Send + Clone,
{
    client: mqtt::AsyncClient,
    tx: Sender<T>,
}

I designed a function for this object to set the message that needs to be sent when MQTT data is received:

#[must_use]
pub fn set_message_callback<S>(self, msg: S) -> Self
where
    S: Fn(mqtt::Message) -> T + 'static,
{
    let tx = self.tx.clone();
    self.client.set_message_callback(move |_cli, data| {
        if let Some(v) = data {
            tx.send(msg(v)).expect("Failed to Send Mqtt Data");
        }
    });
    self
}

I call this function in another thread like this:

enum AppMessage {
    MqttMessage(paho_mqtt::Message),
}

let mqtt_client = AsyncMqttClient::new(tx.clone())
        .set_message_callback(AppMessage::MqttMessage)

Here is the question: in this function, the ā€œmsgā€ object will return a static object. Does this mean that every time MQTT receives a message, it will generate a static variable? Will this eventually lead to a memory leak?

No. A : 'static bound on a type does not mean that the value is alive for the 'static lifetime. It means that the value isn't allowed to contain any temporary (i.e., shorter than 'static) references; in practice, this usually (but not necessarily) implies that the value is an owning value and contains no references at all.

Lifetime annotations don't influence the lifetime of any value anyway; they are only checked against the relevant bindings (i.e., they are constraints, not "commands"). Only owning bindings keep values alive.

In general, it's hard to leak memory by accident in Rust. The language is designed to do the right thing by default. As long as you restrict your data model to a tree-structured ownership scheme (i.e., no cycles using Rc or Arc), and do not use rarely-needed advanced memory management primitives (e.g. interior mutability) or explicitly leak memory (using e.g. mem::forget() or Box::leak()), leaks will be obvious for the most part.

2 Likes

It's a common misconception about what 'static means. Here's an article about it.

In short, 'static bound means you can hold the value forever, it does not means the value will live forever.

let me try to understand this:

S: Fn(mqtt::Message) -> T + 'static

It means S will return a value,It could be a value with a static lifetime or an owned type value. In this code, it will return an ā€œAppMessageā€, which will be droped after exiting the code.

Thanks! This is very helpful for understanding the life time. :+1:

Note that the + puts together multiple bounds; it isn't part of the syntax of the Fn trait and does not apply to the return value.

where
    S: Fn(mqtt::Message) -> T + 'static,

means exactly the same as

where
    S: Fn(mqtt::Message) -> T,
    S: 'static,

and if you wanted to constrain T then you would have to write T: 'static instead. That is, the above is saying that the function of type S must not have any short-lived borrows in it.

3 Likes

Oh, it seems there is still mistake in my previous understanding. It means type ā€œSā€ must have 'static bound; I've checked the code of paho-mqtt, the closure of "set_message_callback" is defined as 'static:

    pub fn set_message_callback<F>(&self, cb: F)
    where
        F: FnMut(&AsyncClient, Option<Message>) + 'static,

In my code, the "msg" object is moved to the closure,so it's must be defined as 'static.
This code is very clear to me now, thanks for your answer !

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.