Rust编程语言入门教程课程笔记
参考教材: The Rust Programming Language (by Steve Klabnik and Carol Nichols, with contributions from the Rust Community)
fn main() {
// Variables and Mutability
let mut x = 5; // mut means mutable
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
// Constants
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3; // all caps with underscores
println!("The value of THREE_HOURS_IN_SECONDS is: {THREE_HOURS_IN_SECONDS}");
// Shadowing
let y = 5;
println!("The value of y is: {y}");
let y = y + 1; //shadowing
// this is allowed because we are creating a new variable
println!("The value of y is: {y}");
// Shadowing vs Mutability
let spaces = " ";
let spaces = spaces.len(); // this is allowed because we are creating a new variable
println!("The value of spaces is: {spaces}");
// Data Types
let guess: u32 = "42".parse().expect("Not a number!"); // type annotation
println!("The value of guess is: {guess}");
let m = 57u8; // u8
println!("The value of m is: {m}");
let n = 1_000_000; // i32
println!("The value of n is: {n}");
let x = 2.0; // f64
let y: f32 = 3.0; // f32
println!("The value of x is: {x}");
println!("The value of y is: {y}");
// addition
let sum = 5 + 10;
println!("The value of sum is: {sum}");
// subtraction
let difference = 95.5 - 4.3;
println!("The value of difference is: {difference}");
// multiplication
let product = 4 * 30;
println!("The value of product is: {product}");
// division
let quotient = 56.7 / 32.2;
let truncated = -5 / 3; // Results in -1
println!("The value of quotient is: {quotient}");
println!("The value of truncated is: {truncated}");
// remainder
let remainder = 43 % 5;
println!("The value of remainder is: {remainder}");
// boolean
let t = true;
let f: bool = false; // with explicit type annotation
println!("The value of t is: {t}");
println!("The value of f is: {f}");
// character
let c = 'z';
let z: char = 'ℤ'; // with explicit type annotation
let heart_eyed_cat = '';
println!("The value of c is: {c}");
println!("The value of z is: {z}");
println!("The value of heart_eyed_cat is: {heart_eyed_cat}");
// Compound Types
// Tuple
let tup: (i32, f64, u8) = (500, 6.4, 1); // type annotation
println!("The value of tup is: {:?}", tup);
let (x, y, z) = tup; // destructuring
println!("The value of x is: {x}");
println!("The value of y is: {y}");
println!("The value of z is: {z}");
let five_hundred = tup.0; // accessing tuple elements
println!("The value of five_hundred is: {five_hundred}");
let six_point_four = tup.1;
println!("The value of six_point_four is: {six_point_four}");
let one = tup.2;
println!("The value of one is: {one}");
//Array
//saved on stack
// fixed length and same type
let a = [1, 2, 3, 4, 5]; // array
println!("The value of a is: {:?}", a);
let a = [3; 5]; // [3, 3, 3, 3, 3] // [element; size]
let first = a[0];
let second = a[1];
println!("The value of first is: {first}");
println!("The value of second is: {second}");
// Functions
another_function(5);
println!("The value of add_five(10) is: {}", add_five(10));
// Control Flow
// if-else flow
let number = 6;
if number % 4 == 0 {
println!("number is divisible by 4");
} else if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2");
}
let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value of number is: {number}");
// loop flow
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // break with value
}// no semicolon here
};// semicolon here
println!("The value of result is: {result}");
// while flow
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}// no semicolon here
println!("LIFTOFF!!!");
// for flow
let a = [10, 20, 30, 40, 50];
for element in a.iter() {// iter() returns each element in a collection
println!("the value is: {element}");
}// no semicolon here
for number in 1..4 {// range [1, 4) = [1, 2, 3]
println!("{}!", number);
}
for number in (1..4).rev() {// reverse
println!("{}!", number);
}
}
fn another_function(x: i32) {// type annotation
println!("Another function.");
println!("The value of x is: {x}");
}
fn add_five(x: i32) -> i32 {
5 + x // no semicolon means return
}
fn main() {
//Ownership is a set of rules that govern how a Rust program manages memory.
//These rules are checked at compile time.
//Ownership is Rust's most unique feature, and it enables Rust to make memory safety guarantees without needing a garbage collector.
//All programs have to manage the way they use a computer's memory while running.
//Stack and Heap
//All data stored on the stack must have a known, fixed size.
//Data with an unknown size at compile time or a size that might change must be stored on the heap instead.
//The main purpose of ownership is to manage heap data.
//Ownership Rules
//1. Each value in Rust has a variable that’s called its owner.
//2. There can only be one owner at a time.
//3. When the owner goes out of scope, the value will be dropped.
//Variable Scope
let mut s = String::from("hello");//The String type is allocated on the heap.
s.push_str(", world!"); // push_str() appends a literal to a String
println!("{}", s); // This will print `hello, world!`
//Memory and Allocation
//With the String type, in order to support a mutable, growable piece of text, we need to allocate an amount of memory on the heap, unknown at compile time, to hold the contents. This means:
//1. The memory must be requested from the memory allocator at runtime.
//2. We need a way of returning this memory to the allocator when we’re done with our String.
//Rust takes a different path:
//the memory is automatically returned once the variable that owns it goes out of scope.
//Move
let x = 5;
let y = x; //Stack-Only Data: Copy
println!("x = {}, y = {}", x, y);//This works because integers are simple values with a known, fixed size, and these two 5 values are pushed onto the stack.
let s1 = String::from("hello");
let s2 = s1;
//println!("{}, world!", s1);//This will throw an error because Rust considers s1 to no longer be valid and, therefore, Rust doesn’t need to free anything when s1 goes out of scope.
println!("{}, world!", s2);//This will work because s2 is the new owner of the String data.
//Clone
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);//This will work because s1.clone() creates a deep copy of the String data, not just a copy of the stack pointer.
//Ownership and Functions
let s = String::from("hello"); // s comes into scope.
takes_ownership(s); // s's value moves into the function...
// ... and so is no longer valid here.
let x = 5; // x comes into scope.
makes_copy(x); // x would move into the function,
// but i32 is Copy, so it’s okay to still
// use x afterward.
//println!("{}", s);//This will throw an error because s is no longer valid.
println!("{}", x);//This will work because x is still valid.
//Return Values and Scope
let s1 = gives_ownership(); // gives_ownership moves its return
// value into s1.
let s2 = String::from("hello"); // s2 comes into scope.
let s3 = takes_and_gives_back(s2); // s2 is moved into
// takes_and_gives_back, which also
// moves its return value into s3.
println!("s1 = {}, s3 = {}", s1, s3);//Here, s3 goes out of scope and is dropped. s2 goes out of scope but was moved, so nothing happens. s1 goes out of scope and is dropped.
//println!("{}", s2);//This will throw an error because s2 is no longer valid.
//References and Borrowing
let s1 = String::from("hello");
let len = calculate_length(&s1);//The &s1 syntax lets us create a reference that refers to the value of s1 but does not own it.
println!("The length of '{}' is {}.", s1, len);//This will work because s1 is still valid.
//Mutable References
let mut s = String::from("hello");
change(&mut s);//We can have only one mutable reference to a particular piece of data in a particular scope.
println!("{}", s);//This will work because s is still valid.
let mut s = String::from("hello");
let r1 = &mut s;
//let r2 = &mut s;//This will throw an error because we can have only one mutable reference to a particular piece of data in a particular scope.
//println!("{}, {}", r1, r2);
println!("{}", r1);
let mut s = String::from("hello");
{
let r1 = &mut s;
println!("{}", r1);
} // r1 goes out of scope here, so we can make a new reference with no problems.
let r2 = &mut s;
println!("{}", r2);
let s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
//let r3 = &mut s; // BIG PROBLEM //This will throw an error because we can have either one mutable reference or any number of immutable references.
println!("{}, {}", r1, r2);
//Note that a reference’s scope starts from where it is introduced and continues through the last time that reference is used.
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// variables r1 and r2 will not be used after this point
let r3 = &mut s; // no problem
println!("{}", r3);
//Dangling References
let reference_to_nothing = dangle();
println!("{}", reference_to_nothing);
//The Rules of References
//1. At any given time, you can have either one mutable reference or any number of immutable references.
//2. References must always be valid.
//The Slice Type
//Slices let you reference a contiguous sequence of elements in a collection rather than the whole collection. A slice is a kind of reference, so it does not have ownership.
//Find the first word in a string
let s = String::from("hello world");
//let wordIndex = first_word(&s);
let first = first_word(&s);
//s.clear();//This will throw an error because we have an immutable reference to s.
println!("the first word is: {}", first);
let hello = &s[..5]; //[starting_index..ending_index) = [0..5) = [0, 1, 2, 3, 4]
let world = &s[6..]; //[starting_index..ending_index) = [6..11) = [6, 7, 8, 9, 10]
println!("{} {}", hello, world);
//Other Slices
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];//[starting_index..ending_index) = [1..3) = [1, 2]
assert_eq!(slice, &[2, 3]);//The assert_eq! macro tests whether two expressions are equal to each other; if they are, nothing happens, and if they aren’t, the macro prints the two expressions and panics.
}
fn takes_ownership(some_string: String) { // some_string comes into scope.
println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing memory is freed.
fn makes_copy(some_integer: i32) { // some_integer comes into scope.
println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.
fn gives_ownership() -> String { // gives_ownership will move its
// return value into the function
// that calls it.
let some_string = String::from("hello"); // some_string comes into scope.
some_string // some_string is returned and
// moves out to the calling
// function.
}
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
// scope.
a_string // a_string is returned and moves out to the calling function.
}
fn calculate_length(s: &String) -> usize {//We call having references as function parameters borrowing.
s.len()
}//Here, s goes out of scope. But because it does not have ownership of what it refers to, nothing happens.
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
// fn dangle() -> &String { //This will throw an error because we are trying to return a reference to a String that is created inside the function.
// let s = String::from("hello");
// &s
// }//Here, s goes out of scope, and is dropped. Its memory goes away. Danger!
fn dangle() -> String {
let s = String::from("hello");
s
}
// fn first_word(s: &String) -> usize {
// let bytes = s.as_bytes();
// for (i, &item) in bytes.iter().enumerate() {
// if item == b' ' {
// return i;
// }
// }
// s.len()
// }
fn first_word(s: &String) -> &str {//We can use &str as the type of the slice parameter to make it clear that the keys function will return slices of String values rather than whole String values.
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {//The enumerate method returns a tuple consisting of the index and the reference to the element.
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
//1. fn first_word(s: &String) -> &str
//2. fn first_word(s: &str) -> &str
//A more experienced Rustacean would write the signature shown in type 2 instead because it allows us to use the same function on both &String values and &str values.