It actually is. The string "hello"
needs to exist somewhere. The compiler will put it right into the compiled binary, and make a
a reference to that part of the binary. This is equivalent to the following code:
static HELLO: &'static str = "hello";
fn call() {
let a = HELLO;
hello(a);
}
There is a second misconception in your post: lifetime annotations cannot affect the semantics of your code. I.e. a
isn't freed not because it's passed to a fn(&'static str)
, but because it is defined as a static string slice. The annotation on the function's parameter determine whether the code will compile, but it won't change the actual runtime lifetimes of your data. Those are determined by the variables' definitions. If they are incompatible with explicit lifetime annotations, you won't get some silent lifetime extension. You'll get a compilation error, and would need to fix it in some way.
As an API design guideline, the function hello
in your example has no business specifying the lifetime as 'static
, since its implementation doesn't depend on the infinite lifetime in any way. Instead, it should have worked with arbitrary lifetime:
fn hello<'lt>(a: &'a str) { .. }
// or, with lifetime elision
fn hello(a: &str) { ... }
Another way to get a static string is to use something like LazyLock<String>
static variable, e.g.
use std::sync::LazyLock;
static STR: LazyLock<String> = LazyLock::new(|| format!("hello, {}", NAME));
This will lazyliy initialize the string on first access, and never free it, so it can give a &'static str
. Note that a LazyLock
can't use any local variables to determine its value. If you need to use local variables, you'd have to use a bit more complex OnceLock
:
use std::sync::OnceLock;
static STR: OnceLock<String> = OnceLock::new();
fn call(name: &str) {
let s: &'static str = STR.get_or_init(|| format!("hello, {}", name));
hello(s);
}