- In the previous demo we saw how to declare a variable
with stack-based storage,
so when you exited the function
the variable automatically disappeared.
In this section we're going to look at static storage.
When you declare a variable with static storage,
it remains allocated permanently,
and it exists until the program terminates, okay?
So you can use that to retain kind of like a global value
would be an example.
So in fact you can define static variables locally
inside a function,
it gets created the first time you call the function,
and then it retains its value until the program terminates,
but it's only visible within that function.
Alternatively, you can declare a static globally,
outside the function, so potentially you could then access
that global variable in any function,
and its value would be retained across the application.
As a separate concept, we have the idea of mutability.
Are you allowed to change the value or not?
That's a separate consideration.
What we're looking at here is the lifetime of the variable.
A static variable lives forever
from the point when it's created.
So there is a difference between statics and consts.
When you declare a static variable,
it is actually a variable,
it gets allocated in memory,
it has a static lifetime, meaning it lives forever,
it has an address,
it physically occupies storage in memory,
and you can take the address of it and you can refer to it.
It could be mutable,
you can declare a mutable static.
You have to be a bit careful, but it is possible.
You have to be careful with thread safety in that case.
But it is possible to have a static
whose value can be mutated.
Consts on the other hand are not allocated in memory at all,
they don't have an address,
they just get substituted directly into your code on usage.
So for example,
if you declared a const called seconds in day,
then the value would be 86,400.
That value would be directly replaced in the code
wherever you use the const.
It's like a #define,
it doesn't actually occupy any storage,
it's just a compile-time constant.
So back to statics, constants being fixed values,
when you define a static variable,
use the static keyword and specify a variable name.
You have to use capital letters.
Well, Rust will give you a warning if you don't.
So static variables have capital letters
to make them look different.
You've got to specify the data type,
type inference is not allowed,
you have to specify the data type and a value, okay?
Typically statics will be fixed, so not mutable,
that's the general theme, it's easier,
although you can have values which are evaluated at runtime,
and we'll see both examples.
Here's a simple example.
I've got a local static variable inside some_func,
the static keyword, here's a static variable,
the name of the variable is MESSAGE,
and it's of type, now this type here, ampersand str,
technically that's a string slice.
We'll have a look at string slices later on,
just for now we'll just treat it as a string,
and we've given it a compile-time string literal, okay?
So this variable here will be initialized
with a compile-time constant.
And that's the easiest way of initializing the string.
Just give it a fixed value, and the compiler knows
what value to initialize it with.
Right, it's also possible to have a static variable
lazily initialized where it isn't a fixed value
like hello, or the number 42,
where the initial value is something
that's gonna be computed at runtime.
This is a bit more tricky.
The simple approach that you might imagine
to do the initialization doesn't actually work.
Have a look at this example,
it would give me a compiler error,
and we'll see an example of the compiler error
in a few minutes.
So I've tried to anyway to declare a static local variable,
TIMESTAMP, of type DateTime is a standard structure in Rust,
and it's a generic,
you've gotta give it a time zone parameter, okay?
So which time zone are you working in?
If you say DateTime,
Utc is Universal Coordinated Time,
it's kind of like Greenwich Mean Time, it's a time zone.
So what I'm trying to do is I'm trying
to initialize this variable at runtime
with the current time.
What I'm hoping to achieve is when the function gets called
for the first time, it'll execute this function call,
it'll determine the current date and time,
and assign the value to this variable
when the function is first called.
And then it'll retain that value thereafter.
This approach won't work because it's not thread safe.
Thread safety is a really important,
a big error in lots of traditional coding
is when you have variables which are kind of accessed
by multiple threads concurrently,
and then you can kind of get thread disputes.
One thread might be in the middle of changing the value
while another thread is also changing the value.
So Rust has very strict rules to preserve thread safety.
It could have been the case that multiple threads
will call in this function at the same time,
and you might end up with a situation
where both threads try to update this variable concurrently,
and that's really not good.
So you can't do it.
If you have a static, you've gotta be a bit more careful
about thread safety, you can't just assign it
a computed value in case multiple threads were trying
to do the same thing concurrently.
So the way that you initialize a static variable
to a runtime value is using something called a Lazy type.
There's a structure type called Lazy.
It's generic.
So you can have a Lazy wrapper around a DateTime,
or a Lazy wrapper around some kind of integer.
Basically, it'll be a value that'll be computed
in a thread-safe manner
the first time you try to initialize it.
Here is the syntax.
So I need to explain quite a few things here.
We'll come back to some of the details later on.
So I'm trying to declare, this will actually work,
I'm trying to declare a static variable called TIMESTAMP.
Lazy is a wrapper, it's a wrapper type,
and you specify what type of value it will contain.
So you can imagine this is quite a good way to envisage it.
You can imagine Lazy as being a wrapper
which will initially not contain a value,
but will evaluate the value inside it.
It's like a box which will be evaluated
in a thread-safe way.
So the value inside the box,
this kind of circle here is going to be a DateTime
of Universal Coordinated Time.
So it's a thread-safe wrapper around a DateTime object.
And what you do is you initialize this Lazy variable,
TIMESTAMP is a Lazy box.
You initialize it like this, you say Lazy new,
we're gonna discuss this syntax
in a lot more detail later on.
But just for now,
structures can have effectively a static method,
typically called new, to create a new instance of Lazy.
So this code here will get evaluated
the first time you actually need to use this variable.
So the first time you use the variable here,
I'll explain the star in a moment as well,
the first time we try to use this Lazy box,
it will be evaluated in a lazy fashion.
This code here in green is called a closure.
It's kind of similar to a lambda in other languages.
It's like an anonymous function.
The vertical bars represent the function parameters,
there are no function parameters here,
it's like a function that doesn't take any parameters,
and it will evaluate this function and return that value.
Whatever this lambda or this closure returns,
the current date and time, that value will be placed
inside the Lazy box at the point when you first evaluate it.
Okay, so at that moment, at the point when you try
to access the value inside the box,
at that point this lambda, or this closure,
will be executed just in time, and whatever it returns,
that value will then be placed
inside the Lazy box like that.
And it invokes that lambda, or that closure,
in a thread-safe manner.
This thread-safe execution is guaranteed.
So our Lazy box, TIMESTAMP is a Lazy variable,
it has inside it a DateTime.
In order to get the contents of that box, you say star,
you deference the Lazy box to get the contents inside it,
like so, okay?
And it'll give you the value that you specified here.
So this is quite tricky.
I'll just summarize,
and then we'll have a natural code example.
I've got a static variable that I wanted to initialize
to be a runtime value.
I wanted to evaluate something at runtime
as opposed to a simple number.
So I wrap it in a Lazy box,
I specify a closure in green to be invoked
when first needed.
The first time I need the value of the timestamp,
only the first time, it will execute that code.
Whatever that code returns,
that value is placed inside the Lazy box,
and my static has now been initialized,
and I can use the star to get the contents
inside that Lazy box.
So you have to work quite hard in Rust
to initialize a static variable at runtime,
but at least it's thread safe.
And this pattern, using the Lazy wrapper,
you'll see this a lot.
It's a shock the first time you see it,
but you'll get used to it.
So the Lazy object wraps a value safely,
the value is initialized on first access,
only the first time you try to access the value,
only then, the first time, will that lambda be executed.
I say lambda, I should say closure.
Most languages would call that a lambda,
but in Rust is called a closure.
And we'll look at closures in a lot of detail later on.
Okay, so access to the variable is thread safe.
This code here is executed in a thread-safe manner,
automatically guaranteed by Rust at the language level.
Thread safety built in.
So you can't go wrong, which is good.