Specialty in Rust
不可视境界线最后变动于:2023年1月1日 晚上
《Rust 程序设计语言》 + Rust语言圣经(Rust Course)
Rust Course
- 使用下划线开头忽略未使用的变量, 以避免警告.
- 变量遮蔽指后遮蔽前变量声明,如果你在某个作用域内无需再使用之前的变量(在被遮蔽后,无法再访问到之前的同名变量),就可以重复的使用变量名字,而不用绞尽脑汁去想更多的名字。
- Rust 是一门静态类型语言, 但是编译器可以自动推导变量类型.
1..5
,生成从 1 到 4 的连续数字,不包含 5 ;1..=5
,生成从 1 到 5 的连续数字,包含 5- Rust 是强类型语言,因此需要你为每一个函数参数都标识出它的具体类型
- 在新的编译器中, 引用作用域的结束位置从花括号变成最后一次使用的位置.
commandline
1 |
|
common concepts
code example
1 |
|
cargo doc --open
doc is stored in the folder.- something special in enumeration waiting for me to be explored
- Rust doesn’t care where you define your functions, only that they’re defined somewhere.
- Statements do not return values. Therefore, you can’t assign a
let
statement to another variable, and can’t use x=y=6 to have both x and y have the value 6 - Rust will do check of every array indexing operation, thus will run slowly
about return statement
- Block is expression, expressions do not include ending semicolons. Most functions return the last expression implicitly. Assigning a block of statements to a variable will get an empty tuple, expressed by “()”.
if expressions
- no parentheses in
return
andif
keyword, Blocks of code associated with the conditions inif
expressions are sometimes called arms - Rust will not automatically try to convert non-Boolean types to a Boolean, do not use integer as the condition of
if
, instead using an integer greater or less than or equal to an certain number. - if else if else if …… may be changed to
match
let number = if condition { 5 } else { 6 };
don’t use different types in each arms.
Repeating
loop
(usebreak
and^C
to terminate) ,while
, andfor
break
can take a value as the return of theloop
- use
for element in a.iter()
to speed up array indexing for
:for number in (1..4).rev()
ownership
Ownership is Rust’s most unique feature, and it enables Rust to make memory safety guarantees without needing a garbage collector.
Memory Management: Memory is managed through a system of ownership with a set of rules that the compiler checks at compile time, won’t slow down your program.
ownership rules:
- Each value in Rust has a variable that’s called its owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
immutable string literals and the changeable String type
create a
String
from a string literal using thefrom
function:let s = String::from("hello");
Rust takes a different path from those languages having GC: the memory is automatically returned once the variable that owns it goes out of scope.
Rust calls a special function for us. This function is called
drop
, and it’s where the author ofString
can put the code to return the memory. Rust callsdrop
automatically at the closing curly bracket.the following code will lead to a move instead of a shallow or deep copy, or you can use
s1.clone()
to do deep copy1
2let s1 = String::from("hello");
let s2 = s1;there is
copy
anddrop
trait, which won’t coexist, the former will leave old value as valid.Ownership and Functions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23fn main() {
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
} // Here, x goes out of scope, then s. But because s's value was moved, nothing
// special happens.
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.Return Values and Scope
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29fn main() {
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
} // 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.
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
}
// takes_and_gives_back will take a String and return one
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
}
References and Borrowing
- we call having references as function parameters borrowing
- Reference’s default immutable
- Use mutabel parameter:
- First, we had to change variable
s
to bemut
. - Then we had to create a mutable reference with
&mut s
- and accept a mutable reference in the function with
some_string: &mut String
.
- First, we had to change variable
- you can have only one mutable reference to a particular piece of data in a particular scope(avoiding race)
A data race is similar to a race condition and happens when these three behaviors occur:
- Two or more pointers access the same data at the same time.
- At least one of the pointers is being used to write to the data.
- There’s no mechanism being used to synchronize access to the data.
The Rust prevents it by giving out an error.
- E0502: A variable already borrowed as immutable was borrowed as mutable.
1 |
|
To fix this error, ensure that you don’t have any other references to the variable before trying to access it mutably:
1 |
|
The slice type
1 |
|
- return a string slice instead of an index
1 |
|
1 |
|
1 |
|
Compound type
array
- 数组是 Rust 的基本类型,是固定长度
1 |
|
stuct
- Rust doesn’t allow us to mark only certain fields as mutable. As with any expression, we can construct a new instance of the struct as the last expression in the function body to implicitly return that new instance.
- Unit-like structs can be useful in situations in which you need to implement a trait on some type but don’t have any data that you want to store in the type itself
1 |
|
- Refactoring with Structs: Adding More Meaning: use struct to wrap relative fields
- print the struct by
println!()
will get an error, and because struct don’t have a provided implementation ofDisplay
, we can only use the#[derived(Debug)]
just before the struct definition. Then use“{:?}”
or“{:#?}”
as the formatter.
Rust has provided a number of traits for us to use with the
derive
annotation that can add useful behavior to our custom types. Those traits and their behaviors are listed in Appendix C. We’ll cover how to implement these traits with custom behavior as well as how to create your own traits in Chapter 10.
- Struct Update Syntax && field init shorthand syntax 两个挺有意思的语法.
- The fields in struct that don’t implement Copy trait will lose ownership during assignment.
Method syntax
definition :they’re defined within the context of a struct (or an enum or a trait object) ……
intention: We’ve put all the things we can do with an instance of a type in one
impl
block rather than making future users of our code search for capabilities ofRectangle
in various places in the library we provide.automatic referencing and dereferencing : when you call a method with
object.something()
, Rust automatically adds in&
,&mut
, or*
soobject
matches the signature of the method. In other words, the following are the same:1
2p1.distance(&p2);
(&p1).distance(&p2);Associated Functions
impl
blocks allow us to define functions withinimpl
blocks that don’t takeself
as a parameter. The usage is just likeString::from
, often used in struct builder.
Enums and Pattern Matching
enum
Defining an enum with variants such as the ones in Listing 6-2 is similar to defining different kinds of struct definitions, except the enum doesn’t use the
struct
keyword and all the variants are grouped together under theMessage
type.we can define methods on struct and the enums.
The
Option
Enum and Its Advantages Over Null ValuesAs such, Rust does not have nulls, but it does have an enum that can encode the concept of a value being present or absent. This enum is
Option<T>
and it is defined by the standard library as follows:1
2
3
4enum Option<T> {
None,
Some(T),
}The
<T>
syntax is generic type parameter.(就跟模板是一样的)So why is having
Option<T>
any better than having null? In short, becauseOption<T>
andT
(whereT
can be any type) are different types, the common_used_operation won’t work.
match
将模式与 target
进行匹配,即为模式匹配
Patterns that Bind to Values
This is how we can extract values out of enum variants.
1 |
|
Matching with Option<T>
1 |
|
Combining
match
and enums is useful in many situations. You’ll see this pattern a lot in Rust code:match
against an enum,- bind a variable to the data inside, and then execute code based on it.
It’s a bit tricky at first, but once you get used to it, you’ll wish you had it in all languages. It’s consistently a user favorite.
Matches Are Exhaustive
- Matches in Rust are exhaustive: we must exhaust every last possibility in order for the code to be valid. Especially in the case of
Option<T>
, when Rust prevents us from forgetting to explicitly handle theNone
case, it protects us from assuming that we have a value when we might have null, thus making the billion-dollar mistake discussed earlier impossible.
The _
Placeholder
- The
_
pattern will match any value.
Concise Control Flow with if let
- In other words, you can think of
if let
as syntax sugar for amatch
that runs code when the value matches one pattern and then ignores all other values.
1 |
|
matches!宏
它可以将一个表达式跟模式进行匹配,然后返回匹配的结果 true
or false
。
1 |
|
Managing Growing Projects with Packages, Crates, and Modules
Common Collections
Vectors
common operation
- Rust provides the
vec!
macro for convenience.let v = vec![1, 2, 3];
orlet v: Vec<i32> = Vec::new();
- use
.push()
to add elements - Dropping a Vector Drops Its Elements
- read elements
- First method is best used when you want your program to crash if there’s an attempt to access an element past the end of the vector.
- When the
get
method is passed an index that is outside the vector, it returnsNone
without panicking
1 |
|
- following code won’t work, because vec may be copied to a new place
1 |
|
- To change the value that the mutable reference refers to, we have to use the dereference operator (
*
) to get to the value ini
before we can use the+=
operator
1 |
|
Using an Enum to Store Multiple Types
- when we need to store elements of a different type in a vector, we can define and use an enum!
1 |
|
string(DOC)
- For that, we use the
to_string
method, which is available on any type that implements theDisplay
trait, as string literals do. - we can find out that the implementation of string is Vec.
CREATE A NEW STRING
1 |
|
UPDATING THE STRING
1 |
|
- pay attention to the ownership taking
1 |
|
- deref coercion: why does the first line compile ? Because Rust compiler can coerce
&String
into a&str
1 |
|
- For more complicated string combining, we can use the
format!
macro
1 |
|
INDEXING
- try to index will lead to an error, because String is a wrapper of Vec<u8>
- You must use range syntax like :
1 |
|
But you can’t simply use the &hello[0..1] because index 1 is not a char boundary.
- Use for_in_ to iterating over strings.
1 |
|
- This trade-off exposes more of the complexity of strings than is apparent in other programming languages, but it prevents you from having to handle errors involving non-ASCII characters later in your development life cycle.
hash map
1 |
|
HashMap 的所有权规则与其它 Rust 类型没有区别:
- 若类型实现
Copy
特征,该类型会被复制进HashMap
,因此无所谓所有权 - 若没实现
Copy
特征,所有权将被转移给HashMap
中
1 |
|
Error Handling
- Rust group errors into two categories: recoverable and unrecoverable errors. Most languages don’t distinguish these two kinds of errors, using mechanism such as exceptions. Rust don’t have exceptions. Instead, it has the type
Result<T, E>
for recoverable errors and thepanic!
macro that stops execution when the program encounters an unrecoverable error.
Generic Types, Traits, and Lifetimes
- we can restrict the types valid for
T
to only those that implementPartialOrd
(or std::fmt::Debug below)
so that can implement generic comparation.
const 泛型: 对值的泛型.
1 |
|
A trait defines functionality a particular type has and can share with other types.
- 孤儿规则: 如果你想要为类型
A
实现特征T
,那么A
或者T
至少有一个是在当前作用域中定义的, 这样别人的代码不会影响你的代码. - 特征可以有默认实现.
1
2
3
4
5
6pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
impl Summary for Weibo { ...... } //为Weibo实现Summary特征- 特征参数. 形如
T: Summary
被称为特征约束.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
//上面的语法糖等同于
pub fn notify<T: Summary>(item: &T) { ...... }
//优势在可以使两个参数都为同一类型.
pub fn notify<T: Summary>(item1: &T, item2: &T) {}
//多重约束
pub fn notify(item: &(impl Summary + Display)) {}
pub fn notify<T: Summary + Display>(item: &T) {}
//where约束简化复杂约束.
fn some_function<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone,
U: Clone + Debug
{}- 待看.
- 孤儿规则: 如果你想要为类型