Few questions about the static rust example

I am trying to understand the static constants (Static - Rust By Example). I understood what are scoped constants are, which cannot be global and cannot be changed. With static (as I also understood) is similar, the only difference they are global and accessible from any part of the code of the same crate (application???).

So that example makes me confused:

// Make a constant with `'static` lifetime.
static NUM: i32 = 18;

// Returns a reference to `NUM` where its `'static`
// lifetime is coerced to that of the input argument.
fn coerce_static<'a>(_: &'a i32) -> &'a i32 {
    &NUM
}

fn main() {
    {
        // Make a `string` literal and print it:
        let static_string = "I'm in read-only memory";
        println!("static_string: {}", static_string);

        // When `static_string` goes out of scope, the reference
        // can no longer be used, but the data remains in the binary.
    }

    {
        // Make an integer to use for `coerce_static`:
        let lifetime_num = 9;

        // Coerce `NUM` to lifetime of `lifetime_num`:
        let coerced_static = coerce_static(&lifetime_num);

        println!("coerced_static: {}", coerced_static);
    }

    println!("NUM: {} stays accessible!", NUM);
}
  1. Can I declare static inside main function? Who/what can access it? What happens if I declare it inside of scope { static my_var: i32 = 5; }?
  2. I don't understand the purpose of the function coerce_static(). To me it literally receives a reference of something and returns a reference of static binary (it does not do anything with the method's parameter). It does nothing in this example.
  3. What does a mean? Should it be fn coerce_static<'a>(_: &'a i32) -> &'a i32 where a: static? And what that " ' " character for?
  4. The line println!("NUM: {} stays accessible!", NUM); is simply returns the static variable which is out of the current scope (out of fn main()). What author wanted to tell with that example? If I declare a non static variable in the parent scope it would be the same?
  5. Can I access static from other crates? Should it be pub?

If someone could answer those questions in very simple way. I am trying to understand rust and I could not. I hope you can help me with that.
Thanks.

1 Like

Sure. It's still a static, there's just no way to name it outside of the function. It's similar to the static_string example (which would have been better named static_str).

Rust has a concept of lifetimes to track the borrowing of owned values (and related ideas). It's a large topic, but I'll try to summarize. Lifetimes start with ', like 'a or 'static. All references like &i32 have a lifetime, for example, although often you can not write out the lifetime and leave it implicit. For references, when you do write the lifetime, it goes after the &, like &'a i32. A type with a lifetime is only valid where the lifetime is valid.

'static means "valid forever". The other lifetimes (like 'a) you'll see are parameters -- sort of like variables -- that are either implicit (and have no name), or that you have to declare (and given them a name). (You may also see '_, which means "infer the lifetime for me" or "any lifetime will do", depending on the context.)

So fn coerce_static<'a>(_: &'a i32) is a function that has a lifetime parameter. It's declared by mentioning it in the <...>. The lifetime is part of the type of the reference it takes in, and it returns a reference with the same type (including the lifetime). The function is generic over the lifetime.

The caller chooses the "value" that parameters take on, so this function declaration says "I can take a &'_ i32 with any lifetime you choose, and return a &'_ i32 with that same lifetime."

It demonstrates that a &'static _ can be coerced to any other lifetime: The caller chooses how long the lifetime 'a is, but no matter what they choose, you can return &NUM -- because you can create a &'static NUM and coerce that to any shorter lifetime.

A &'static NUM, which is valid everywhere, can be coerced into any shorter lifetime -- it's valid for any other lifetime too. (There are other situations where lifetimes cannot be shortened, though.)

Here's what they were trying to convey, I think:

    {
        // This local variable drops at the end of this block.
        //
        // It would be invalid to have a reference to it after the block,
        // and Rust forbids invalid references, so it is impossible to
        // create a `&'static` to this local variable.
        let lifetime_num = 9;

        // That is, the lifetime attached to the reference here
        // cannot last beyond the block either -- cannot be `'static`.
        //
        // And the type of `coerced_static` is the same as the input type
        // (including lifetime) due to the function signature.  So the
        // `&NUM` was definitely coerced to a `&i32` with a lifetime
        // that is not `'static`.
        let coerced_static = coerce_static(&lifetime_num);
        // The lifetime has to still be valid here because it is used.
        println!("coerced_static: {}", coerced_static);
    }
    // But the lifetime must be invalid now, beyond the block.
    //
    // However, just because `&NUM` was coerced to a `&'not_a_static i32`
    // doesn't mean `NUM` itself becomes inaccessible.
    println!("NUM: {} stays accessible!", NUM);

Yes, you could create a reference to a variable in the parent scope with a lifetime limited by the inner scope, and the parent would still be valid after the inner scope.

If privacy allows it, you can access static values from other crates. Sometimes you want that, sometimes you don't.


Finally, some quibles about that page:

There are two ways to make a variable with 'static lifetime, and both are stored in the read-only memory of the binary:

  • Make a constant with the static declaration
  • Make a string literal which has type &'static str
  • There are other ways to make 'static values, like leaking memory

    • Which can be on the heap, not static memory
  • "Make a constant" is misleading wording, as const variables in Rust are different than static ones. const values are more like values that are instantiated independently everywhere you use them. They might just be put on the stack or in a register when you call a function, for example.

  • "string literal" should just be phrased str or &str literal, to avoid confusion with the String type (which stores values in the heap, can shrink or grow in length, etc.)

  • str is not the only thing you can create an implicit (or explicit) &'static to. For example, here's an i32 being promoted.

3 Likes

Thanks Quine for the wonderful explanation. I feel I understood/(makes sense to me) 75% of it which is a lot.

The only thing with const. I am thinking about constants like data blobs which will be included ("printed") into compiled binaries as it is. Like some kind of stack memory allocation for a sake of the current scope. Am I right? Or rust does it in a different way?

While the static (thanks again for your help) are variables can be declared anywhere, can be mutable because they are variables (stack or heap) and can have an access based on the declaration.

1 Like

Rust has some freedom here, but to quote an expert:

Each use of a const is basically equivalent to inlining the const definition at that place

and that could get optimized any number of ways. In the end you might end up with one copy of the data in the binary, or with many copies, or with no copies (optimized away as never used, or a constant 0 replaced with xoring a register value away, or an assignment replaced with a call to memset, or....).

While the static (thanks again for your help) are variables cna be declared anywhere, can be mutable because they are variables (stack or heap) and can have an access based on the declaration.

Take care with the distinction between static references -- references with a static lifetime (&'static _), and static-declared items. The article you link did draw a distinction there, but not completely successfully in my opinion (the quibles).

static items will have a precise location in your program, usually in read-only memory. The item won't be on the stack. You can declare these as mut, but you need unsafe to use a static mut, and it is so hard to use a static mut soundly that it may be deprecated. Access is based on the declaration (privacy and scope). Values known at compile time (like literals) may be promoted to be static items (e.g. if you take a &'static reference to one).

One of the biggest footguns with static mut items is that it's too easy to create a &'static mut reference to them, and in Rust it is undefined behavior to have a &mut to something which is accessible by any means other than that &mut. ("Unique reference" would have been a better name than "mutable reference").

&'static references are more general static items. This is a reference that is valid "forever", so the data it references must be valid forever (until the program ends) too. static items qualify here -- you can create a &'static that references a static item. But you can also leak memory to create a &'static referencing the heap. It can even be a &'static mut in this case, because a Box owns the data within it -- so when you leak a Box, it can guarantee there is only the one unique borrow (only the one &mut). In this case there's no static mut item that is accessible from many places.

3 Likes

I am very thankful for your answer @quinedot and all your effort to keep things clear. I am getting it.
I have a small feeling it has so many if-s I cannot place in my head, or if I do, I would not be able to communicate with my family and friends anymore. :wink:

I wish it could be much simpler. :love_you_gesture:

1 Like

If you don't recognize the 'a syntax, that means you haven't heard about lifetimes yet. Lifetimes are a fundamental concept in Rust. If you don't know about them, please read the Book first! It's very hard to concisely and reasonably explain the entirety (or even the basics!) of the language in a forum-post-sized piece. You should familiarize yourself with the absolute basics at least before asking here, at the very minimum at the level of mere syntactic recognition, otherwise it will be extremely difficult for others to help you. Forum Q&A's are not the right format for teaching a programming language from scratch; that's what the (excellent) Book is for. It would be way more productive to ask here about specific, narrower problems.

1 Like

Thanks. I will read lifetimes. At that point I didn't know that.