24 days from Node.js to Rust
preface
The lifecycle concept of Rust is focused on avoiding empty references, which can cause an application to crash for users and can be a vulnerability for malicious attackers.
Note: Empty references are the source of most software errors. According to the Chromium team, 70% of serious accidents are memory-safe (see article), so if you’re interested, read this article about browser memory safety
The memory security issue is a bit unfamiliar to developers who have never used a language like C for memory management. JavaScript and Java use garbage collection to manage your memory and avoid empty references. The garbage collector tracks your references to data until the number of references to data is zero
Just 63 years ago, garbage collection was very, very revolutionary, but it came at a cost. In extreme cases, garbage collection made programs unresponsive for a few seconds
Rust does not use garbage collection to avoid empty references, and you can get the efficiency of C with the security of JavaScript
Note: There are many articles on the Rust lifecycle online. This tutorial will not cover everything, but just some of the confusing questions. The subsequent readings are a prerequisite for reading this article
The body of the
Lifecycle & Lifecycle annotations
You’ll often see “lifecycle annotations” shortened to “lifecycle” in articles, but this is just confusing
- Life cycle:
Rust
Borrow inspector(borrow checkerEvery variable has a point in time when it is created and destroyed, which is its life cycle - Life cycle Notes: One kind
Rust
Developers can add a named tag to a reference to give it a lifetime, which makes sense when the scenario is multiple references
When you read “you must specify the life cycle” or “give a reference a life cycle”, the “life cycle” here is really “life cycle annotation”.
Lifecycle annotations are omitted
Every reference has a life cycle, regardless of whether there are annotations or not, and sometimes not annotating does not mean avoiding the life cycle
For example, this function:
fn omits_annotations(list: &[String]) -> OptionThe < &String> {
list.get(0)}Copy the code
Is equivalent to the following line number:
fn has_annotations<'a>(list: &'a [String]) -> OptionThe < &'a String> {
list.get(1)}Copy the code
Use is also consistent:
fn main() {
let authors = vec!["Samuel Clemens".to_owned(), "Jane Austen".to_owned()];
let value = omits_annotations(&authors).unwrap();
println!("The first author is '{}'", value);
let value = has_annotations(&authors).unwrap();
println!("The second author is '{}'", value);
}
Copy the code
Output result:
The first author is 'Samuel Clemens'
The second author is 'Jane Austen'
Copy the code
The annotation can be omitted because there is only one input parameter and one return value, and Rust automatically concludes that their lifecycles are the same
Static declaration cycle (‘static)
The Rust community often refers to static, and there are many explanations for static, but this makes it more complicated, so let’s make it as brief as possible
There are two classic use scenarios for ‘static ‘:
(I) Explicit lifetime comments as references
fn main() {
let mark_twain = "Samuel Clemens";
print_author(mark_twain);
}
fn print_author(author: &'static str) {
println!("{}", author);
}
Copy the code
(2) life cycle limits as generic parameters
fn print<T: Display + 'static>(message: &T) {
println!("{}", message);
}
Copy the code
The presence of static means that the reference is valid for the rest of the program, and the data to which it points cannot be moved or changed. String literals are &’static,
Static does not apply to a variable that holds a reference. The get_memory_location() function returns a pointer and length to a string literal, and the get_str_at_location() function takes a pointer and length and reads the contents of the specified location in memory
use std::{slice::from_raw_parts, str::from_utf8_unchecked};
fn get_memory_location() - > (usize.usize) {
let string = "Hello World!";
let pointer = string.as_ptr() as usize;
let length = string.len();
(pointer, length)
// `string` is dropped here.
// It's no longer accessible, but the data lives on.
}
fn get_str_at_location(pointer: usize, length: usize) - > &'static str {
// Notice the `unsafe {}` block. We can't do things like this without
// acknowledging to Rust that we know this is dangerous.
unsafe { from_utf8_unchecked(from_raw_parts(pointer as *const u8, length)) }
}
fn main() {
let (pointer, length) = get_memory_location();
let message = get_str_at_location(pointer, length);
println!(
"The {} bytes at 0x{:X} stored: {}",
length, pointer, message
);
// If you want to see why dealing with raw pointers is dangerous,
// uncomment this line.
// let message = get_str_at_location(1000, 10);
}
Copy the code
The output is:
The 12 bytes at 0x562037200057 stored: Hello World!
Copy the code
On the other hand, adding ‘static ‘as a constraint tells Rust that you want the type to persist, not the data
You need to understand that &’static ‘does not equal T: ‘static
The following code confirms how the String in the second block satisfies the static_bound() ‘static constraint without retaining the same properties of &’static in the first block
use std::fmt::Display;
fn main() {
let r1;
let r2;
{
static STATIC_EXAMPLE: i32 = 42;
r1 = &STATIC_EXAMPLE;
let x = "&'static str";
r2 = x;
}
println!("&'static i32: {}", r1);
println!("&'static str: {}", r2);
let r3;
{
let string = "String".to_owned();
static_bound(&string); // This is *not* an error
r3 = &string; // *This* is
}
println!("{}", r3);
}
fn static_bound<T: Display + 'static>(t: &T) {
println!("{}", t);
}
Copy the code
Results:
error[E0597]: `string` does not live long enough
--> crates/day-16/static/src/main.rs:21:10
|
21 | r3 = &string;
| ^^^^^^^ borrowed value does not live long enough
22 | }
| - `string` dropped here whilestill borrowed 23 | println! ("{}", r3);
| -- borrow later used here
For more information about this error, try `rustc --explain E0597`.
Copy the code
Although the two uses are related, the spirit behind them is different
If you want to solve the problem by adding &’static, then you need to rethink and see if it’s the right thing to do. If you need to add ‘static to solve the problem, that’s usually fine
reading
- The Rust Book: ch 10.03 – Validating References with Lifetimes
- Rust by Example: Lifetimes
- Rust Reference: Trait and lifetime bounds
- Rust Reference: Lifetime elision
- Rustonomicon: Lifetimes
- Common Rust Lifetime Misconceptions
- rustviz: Rust lifetime visualizer
- Understanding lifetimes in Rust
conclusion
After five iterations of this article, the biggest headaches are generics, life cycles, best practices in reference to third-party libraries, and asynchronous code, which we haven’t covered yet.
We will cover how to use Rust to improve the performance of JavaScript applications, how to build a Web server, how to read and write files, and how to serialize and deserialize JSON