变量绑定默认是不可变的
let可以使用模式
let (x, y) = (1, 2);
类型推断
let x: i32 = 5;
变量使用之前必须初始化
变量作用域仅在一个被{}包围的语句块中
变量可以被隐藏。这意味着一个后声明的并位于同一作用域的相同名字的变量绑定将会覆盖前一个变量绑定
函数参数需要声明类型
Rust 函数只能返回一个值,并且你需要在一个“箭头”后面声明类型,它是一个破折号(-)后跟一个大于号(>)
表达式结尾不加分号,语句结尾加分号
表达式返回一个值而语句不是,语句返回一个空元组
rust中,使用return是一个糟糕的风格
发散函数并不返回,使用->!来代表发散函数
发散函数可以被用作任何类型
let f: fn(i32) -> i32;
f是一个指向一个获取i32作为参数并返回i32的函数的变量绑定。
可以用f来调用这个函数
bool char 数字类型
数组的类型是[T; N]。我们会在泛型部分的时候讨论这个T标记。N是一个编译时常量,代表数组的长度。
有一个可以将数组中每一个元素初始化为相同值的简写。在这个例子中,a的每个元素都被初始化为0:
let a = [0; 20]; // a: [i32; 20]
切片
你可以用一个&和[]的组合从多种数据类型创建一个切片。&表明切片类似于引用,这个我们会在本部分的后面详细介绍。带有一个范围的[],允许你定义切片的长度:
片段拥有&[T]类型。let a = [0, 1, 2, 3, 4];
let complete = &a[..]; // A slice containing all of the elements in alet middle = &a[1..4]; // A slice of a: just the elements 1, 2, and 3
str 原始字符串,不定长类型
元组
let (x, y, z) = (1, 2, 3);
可以使用let来解构元组
你可以一个逗号来消除一个单元素元组和一个括号中的值的歧义:
(0,); // single-element tuple
(0); // zero in parentheses
// 行注释
/// 文档注释
//! 包含注释
if不需要用括号
loop 死循环
while
for var in expression {
code
}
ownership
Rust 中的变量绑定有一个属性:它们有它们所绑定的的值的所有权。这意味着当一个绑定离开作用域,它们绑定的资源就会被释放
*move semantics
Rust 确保了对于任何给定的资源都正好(只)有一个绑定与之对应,我们把所有权转移给别的别的绑定时,我们说我们“移动”了我们引用的值。
*copy 类型*
我们已经知道了当所有权被转移给另一个绑定以后,你不能再使用原始绑定。然而,有一个trait会改变这个行为,它叫做Copy。
一个i32,它实现了Copy。这意味着,就像一个移动,当我们把v赋值给v2,产生了一个数据的拷贝。不过,不像一个移动,我们仍可以在之后使用v。这是因为i32并没有指向其它数据的指针,对它的拷贝是一个完整的拷贝。
所有基本类型都实现了Copy trait,因此他们的所有权并不像你想象的那样遵循“所有权规则”被移动。
所有权之外
借用
与其直接传递v1和v2,我们传递&v1和&v2。我们称&T类型为一个”引用“,而与其拥有这个资源,它借用了所有权。一个借用变量的绑定在它离开作用域时并不释放资源。
&mut引用
&mut T。一个“可变引用”允许你改变你借用的资源,一个&mut引用。你也需要使用他们(星号)来访问引用的内容。
规则
第一,任何借用必须位于比拥有者更小的作用域。第二,同一个作用域下,不能同时存在可变和不可变的引用:
0 个或 N 个资源的引用(&T)
只有 1 个可变引用(&mut T)
释放后使用
引用必须与它引用的值存活得一样长。Rust 会检查你的引用的作用域来保证这是正确的。
Rust 所有权系统通过一个叫生命周期(lifetime)的概念来做到这一点,它定义了一个引用有效的作用域。
'a读作“生命周期 a”。技术上讲,每一个引用都有一些与之相关的生命周期,不过编译器在通常情况让你可以省略(也就是,省略,查看下面的生命周期省略)它们。
static
叫做static的作用域是特殊的。它代表某样东西具有横跨整个程序的生命周期。大部分 Rust 程序员当他们处理字符串时第一次遇到'static
生命周期省略
有3条规则:
每一个被省略的函数参数成为一个不同的生命周期参数。
如果刚好有一个输入生命周期,不管是否省略,这个生命周期被赋予所有函数返回值中被省略的生命周期。
如果有多个输入生命周期,不过它们当中有一个是&self或者&mut self,self的生命周期被赋予所有省略的输出生命周期。
否则,省略一个输出生命周期将是一个错误。
更新语法
一个包含..的struct表明你想要使用一些其它结构体的拷贝的一些值。例如:
struct Point3d {
x: i32,
y: i32,
z: i32,
}
let mut point = Point3d { x: 0, y: 0, z: 0 };
point = Point3d { y: 1, .. point };
元组结构体
Rust有像另一个元组和结构体的混合体的数据类型。元组结构体有一个名字,不过它的字段没有。他们用struct关键字声明,并元组前面带有一个名字:
不过有种情况元组结构体非常有用,就是当元组结构体只有一个元素时。我们管它叫新类型(newtype),因为你创建了一个与元素相似的类型:
struct Inches(i32);
let length = Inches(10);
let Inches(integer_length) = length;
println!("length is {} inches", integer_length);
如你所见,你可以通过一个解构let来提取内部的整型,就像我们在讲元组时说的那样,let Inches(integer_length)给integer_length赋值为10。
类单元结构体
这样的结构体叫做“类单元”因为它与一个空元组类似,(),这有时叫做“单元”。就像一个元组结构体,
它定义了一个新类型。就它本身来看没什么用(虽然有时它可以作为一个标记类型),不过在与其它功能的结合中,它可以变得有用。
例如,一个库可能请求你创建一个实现了一个特定特性的结构来处理事件。如果你并不需要在结构中存储任何数据,
你可以仅仅创建一个类单元结构体。
Rust 中的一个enum是一个代表数个可能变量的数据的类型。每个变量都可选是否关联数据:
enum Message {
Quit,
ChangeColor(i32, i32, i32),
Move { x: i32, y: i32 },
Write(String),
}
定义变量的语法与用来定义结构体的语法类似:你可以有不带数据的变量(像类单元结构体),带有命名数据的变量,和带有未命名数据的变量(像元组结构体)。然而,不像单独的结构体定义,一个enum是一个单独的类型。一个枚举的值可以匹配任何一个变量。因为这个原因,枚举有时被叫做“集合类型”:枚举可能值的集合是每一个变量可能值的集合的总和。
match
match强制穷尽性检查,所有分支必须都做处理
match也是一个表达式,也就是说它可以用在let绑定的右侧或者其它直接用到表达式的地方
匹配枚举
match的另一个重要的作用是处理枚举的可能变量:
enum Message {
Quit,
ChangeColor(i32, i32, i32),
Move { x: i32, y: i32 },
Write(String),
}
fn quit() { }
fn change_color(r: i32, g: i32, b: i32) { }
fn move_cursor(x: i32, y: i32) {}
fn process_message(msg: Message) {
match msg {
Message::Quit => quit(),
Message::ChangeColor(r, g, b) => change_color(r, g, b),
Message::Move { x: x, y: y } => move_cursor(x, y),
Message::Write(s) => println!("{}", s),
};
}
模式十分强大
记住几个准则
match匹配的内容如果是常量或者枚举时,是我们传统意义理解的比较,其他的统统是表达式,是做的模式匹配。
方法调用
self如果它只是栈上的一个值,&self如果它是一个引用,然后&mut self如果它是一个可变引用
我们应该默认使用&self,就像相比获取所有权你应该更倾向于借用,同样相比获取可变引用更倾向于不可变引用一样
链式方法调用
关联函数
注意静态函数是通过Struct::method()语法调用的,而不是ref.method()语法。
创建者模式
对于重复初始值有另一种形式的vec!:
let v = vec![0; 10]; // ten zeroes
另外值得注意的是必须用usize类型的值来索引
如果尝试访问并不存在的索引,么当前的线程会 panic并输出信息,如果你想处理越界错误而不是 panic,你可以使用像get
或get_mut
这样的方法,他们当给出一个无效的索引时返回None:
let v = vec![1, 2, 3];
match v.get(7) {
Some(x) => println!("Item 7 is {}", x),
None => println!("Sorry, this vector is too short.")
}
迭代
可以用for来迭代 vector 的元素,迭代有三个版本,&,&mut,take ownership
一个字符串是一串UTF-8字节编码的Unicode量级值的序列。所有的字符串都确保是有效编码的UTF-8序列。另外,字符串并不以null结尾并且可以包含null字节
fn takes_anything(x: T) {
// do something with x
}
函数泛型
fn takes_anything(x: T) {
// do something with x
}
结构体泛型
struct Point {
x: T,
y: T,
}
trait HasArea {
fn area(&self) -> f64;
}
fn print_area(shape: T) {
println!("This shape has an area of {}", shape.area());
}
语法是指any type that implements the HasArea trait(任何实现了HasAreatrait的类型)。
类似其他语言中的接口的概念
泛型结构体的trait绑定
impl Rectangle { ... }
实现trait的规则
第一是如果 trait 并不定义在你的作用域,它并不能实现。
这还有一个实现trait的限制。不管是trait还是你写的impl都只能在你自己的包装箱内生效。
所以,我们可以为i32实现HasAreatrait,因为HasArea在我们的包装箱中。不过如果我们想为i32实现Floattrait,它是由Rust提供的,则无法做到,因为这个trait和类型都不在我们的包装箱中。
多trait绑定
如果你需要多于1个限定,可以使用+:
use std::fmt::Debug;
fn foo(x: T) {
x.clone();
println!("{:?}", x);
}
T现在需要实现Clone和Debug。
where从句
如果你需要多于1个限定,可以使用+:
use std::fmt::Debug;
fn foo(x: T) {
x.clone();
println!("{:?}", x);
}
T现在需要实现Clone和Debug。
默认方法
trait Foo {
fn is_valid(&self) -> bool;
fn is_invalid(&self) -> bool { !self.is_valid() }
}
默认方法可以不实现,也可以覆盖
impl Foo for OverrideDefault {
fn is_valid(&self) -> bool {
println!("Called OverrideDefault.is_valid.");
true
}
fn is_invalid(&self) -> bool {
println!("Called OverrideDefault.is_invalid!");
true // overrides the expected value of is_invalid()
}
}
继承
有时,实现一个trait要求实现另一个trait:
// trait Foo {
// fn foo(&self);
// }
// trait FooBar : Foo {
// fn foobar(&self);
// }
struct Baz;
impl Foo for Baz {
fn foo(&self) { println!("foo"); }
}
impl FooBar for Baz {
fn foobar(&self) { println!("foobar"); }
}
deriving
重复的实现像Debug和Default这样的 trait 会变得很无趣。为此,Rust 提供了一个属性来允许我们让 Rust 为我们自动实现 trait:
` #[derive(Debug)]struct Foo;
fn main() {
println!("{:?}", Foo);
}
然而,deriving 限制为一些特定的 trait:
Clone
Copy
Debug
Default
Eq
Hash
Ord
PartialEq
PartialOrd
Drop trait提供了一个当一个值离开作用域后运行一些代码的方法。例如:
struct HasDrop;
impl Drop for HasDrop {
fn drop(&mut self) {
println!("Dropping!");
}
}
fn main() {
let x = HasDrop;
// do stuff
} // x goes out of scope here
值会以与它们声明相反的顺序被丢弃(dropped)
Drop用来清理任何与struct关联的资源
if let允许你合并if和let来减少特定类型模式匹配的开销
如果一个模式匹配成功,它绑定任何值的合适的部分到模式的标识符中,并计算这个表达式。如果模式不匹配,啥也不会发生。
如果你想在模式不匹配时做点其他的,你可以使用else:
# let option = Some(5);
# fn foo(x: i32) { }
# fn bar() { }
if let Some(x) = option {
foo(x);
} else {
bar();
}
while let
类似的,当你想一直循环,直到一个值匹配到特定的模式的时候,你可以选择使用while let。使用while let可以把类似这样的代码:
let mut v = vec![1, 3, 5, 7, 11];
loop {
match v.pop() {
Some(x) => println!("{}", x),
None => break,
}
}
变成这样的代码:
let mut v = vec![1, 3, 5, 7, 11];
while let Some(x) = v.pop() {
println!("{}", x);
}
当涉及到多态的代码时,我们需要一个机制来决定哪个具体的版本应该得到执行。这叫做“分发”(dispatch)。大体上有两种形式的分发:静态分发和动态分发。虽然 Rust 喜欢静态分发,不过它也提供了一个叫做“trait 对象”的机制来支持动态分发。
静态分发
//# trait Foo { fn method(&self) -> String; }
//# impl Foo for u8 { fn method(&self) -> String { format!("u8: {}", *self) } }
//# impl Foo for String { fn method(&self) -> String { format!("string: {}", *self) } }
fn do_something(x: T) {
x.method();
}
fn main() {
let x = 5u8;
let y = "Hello".to_string();
do_something(x);
do_something(y);
}
静态分发能提高程序的运行效率,不过相应的也有它的弊端:会导致“代码膨胀”(code bloat)。因为在编译出的二进制程序中,同样的函数,对于每个类型都会有不同的拷贝存在。
编译器也不是完美的并且“优化”后的代码可能更慢。例如,过度的函数内联会导致指令缓存膨胀(缓存控制着我们周围的一切)。这也是为何要谨慎使用#[inline]和#[inline(always)]的部分原因。另外一个使用动态分发的原因是,在一些情况下,动态分发更有效率。
然而,常规情况下静态分发更有效率,并且我们总是可以写一个小的静态分发的封装函数来进行动态分发,不过反过来不行,这就是说静态调用更加灵活。因为这个原因标准库尽可能的使用了静态分发。
动态分发
Rust 通过一个叫做“trait 对象”的功能提供动态分发。比如说&Foo、Box这些就是trait对象。它们是一些值,值中储存实现了特定 trait 的任意类型。它的具体类型只能在运行时才能确定。
从一些实现了特定trait的类型的指针中,可以从通过转型(casting)(例如,&x as &Foo)或者强制转型(coercing it)(例如,把&x当做参数传递给一个接收&Foo类型的函数)来取得trait对象。
这些 trait 对象的强制多态和转型也适用于类似于&mut Foo的&mut T以及Box的Box这样的指针,也就是目前为止我们讨论到的所有指针。强制转型和转型是一样的。
这个操作可以被看作“清除”编译器关于特定类型指针的信息,因此trait对象有时被称为“类型清除”(type erasure)。
//# trait Foo { fn method(&self) -> String; }
//# impl Foo for u8 { fn method(&self) -> String { format!("u8: {}", *self) } }
//# impl Foo for String { fn method(&self) -> String { format!("string: {}", *self) } }
fn do_something(x: &Foo) {
x.method();
}
fn main() {
let x = 5u8;
do_something(&x as &Foo);//或者do_something(&x);
}
一个使用trait对象的函数并没有为每个实现了Foo的类型专门生成函数:它只有一份函数的代码,一般(但不总是)会减少代码膨胀。然而,因为调用虚函数,会带来更大的运行时开销,也会大大地阻止任何内联以及相关优化的进行。
表现(representation)
类似c++虚表
对象安全
并不是所有 trait 都可以被用来作为一个 trait 对象只有对象安全的 trait 才能成为 trait 对象。一个对象安全的 trait 需要如下两条为真:
trait 并不要求Self: Sized
所有的方法是对象安全的
那么什么让一个方法是对象安全的呢?每一个方法必须要求Self: Sized或者如下所有:
必须没有任何类型参数
必须不使用Self
几乎所有的规则都谈到了Self。一个直观的理解是“除了特殊情况,如果你的 trait 的方法使用了Self,它就不是对象安全的”。
语法
有时为了整洁和复用打包一个函数和自由变量(free variables)是很有用的。自由变量是指被用在函数中来自函数内部作用域并只用于函数内部的变量。
记住{}是一个表达式,所以我们也可以拥有包含多行的闭包
闭包及环境
之所以把它称为“闭包”是因为它们“包含在环境中”(close over their environment)。这看起来像:
let num = 5;
let plus_num = |x: i32| x + num;
assert_eq!(10, plus_num(5));
这个闭包,plus_num,引用了它作用域中的let绑定:num。更明确的说,它借用了绑定。如果我们做一些会与这个绑定冲突的事,我们会得到一个错误。比如这个:
+
let mut num = 5;
let plus_num = |x: i32| x + num;
let y = &mut num;
然而,如果你的闭包需要它,Rust会取得所有权并移动环境。这个不能工作:
let nums = vec![1, 2, 3];
let takes_nums = || nums;
println!("{:?}", nums);
move闭包
let mut num = 5;
{
let mut add_num = move |x: i32| num += x;
add_num(5);
}
assert_eq!(5, num);
我们只会得到5。与其获取一个我们num的可变借用,我们取得了一个拷贝的所有权。
另一个理解move闭包的方法:它给出了一个拥有自己栈帧的闭包。没有move,一个闭包可能会绑定在创建它的栈帧上,而move闭包则是独立的。例如,这意味着大体上你不能从函数返回一个非move闭包。
闭包的实现
闭包作为参数
静态分发
fn call_with_one(some_closure: F) -> i32where F : Fn(i32) -> i32 {
some_closure(1)
}
let answer = call_with_one(|x| x + 2);
assert_eq!(3, answer);
动态分发
fn call_with_one(some_closure: &Fn(i32) -> i32) -> i32 {
some_closure(1)
}
let answer = call_with_one(&|x| x + 2);
assert_eq!(3, answer);
函数指针和闭包
一个函数指针有点像一个没有环境的闭包。因此,你可以传递函数指针给任何期待闭包参数的函数,且能够工作:
fn call_with_one(some_closure: &Fn(i32) -> i32) -> i32 {
some_closure(1)
}
fn add_one(i: i32) -> i32 {
i + 1
}
let f = add_one;
let answer = call_with_one(&f);
assert_eq!(2, answer);
返回闭包
fn factory() -> Box i32> {
let num = 5;
Box::new(move |x| x + num)
}
# fn main() {
let f = factory();
let answer = f(1);
assert_eq!(6, answer);
# }
我们需要一个区分我们需要调用哪一函数的方法。这个功能叫做“通用函数调用语法”
# trait Foo {
# fn f(&self);
# }
# trait Bar {
# fn f(&self);
# }
# struct Baz;
# impl Foo for Baz {
# fn f(&self) { println!("Baz’s impl of Foo"); }
# }
# impl Bar for Baz {
# fn f(&self) { println!("Baz’s impl of Bar"); }
# }
# let b = Baz;
Foo::f(&b);
Bar::f(&b);
尖括号形式
::method(args);
<>::语法是一个提供类型提示的方法。类型位于<>中。在这个例子中,类型是Type as Trait,表示我们想要method的Trait版本被调用。在没有二义时as Trait部分是可选的。尖括号也是一样。因此上面的形式就是一种缩写的形式。
trait Foo {
fn foo() -> i32;
}
struct Bar;
impl Bar {
fn foo() -> i32 {
20
}
}
impl Foo for Bar {
fn foo() -> i32 {
10
}
}
fn main() {
assert_eq!(10, ::foo());
assert_eq!(20, Bar::foo());
}
使用尖括号语法让你可以调用指定trait的方法而不是继承到的那个
包装箱和模块
包装箱是其它语言中库(library)或包(package)的同义词。因此“Cargo”则是Rust包管理工具的名字:你通过Cargo把你当包装箱交付给别人。包装箱可以根据项目的不同生成可执行文件或库文件。
每个包装箱有一个隐含的根模块(root module)包含模块的代码。你可以在根模块下定义一个子模块树。模块允许你为自己模块的代码分区。
+-----------+
+---| greetings |
| +-----------+
+---------+ |
+---| english |---+
| +---------+ | +-----------+
| +---| farewells |
+---------+ | +-----------+
| phrases |---+
+---------+ | +-----------+
| +---| greetings |
| +----------+ | +-----------+
+---| japanese |--+
+----------+ |
| +-----------+
+---| farewells |
+-----------+
定义模块
我们用mod关键字来定义我们的每一个模块。让我们把src/lib.rs写成这样:
mod english {
mod greetings {
}
mod farewells {
}
}
mod japanese {
mod greetings {
}
mod farewells {
}
}
多文件包装箱
$ tree .
.
├── Cargo.lock
├── Cargo.toml
├── src
│ ├── english
│ │ ├── farewells.rs
│ │ ├── greetings.rs
│ │ └── mod.rs
│ ├── japanese
│ │ ├── farewells.rs
│ │ ├── greetings.rs
│ │ └── mod.rs
│ └── lib.rs
└── target
└── debug
├── build
├── deps
├── examples
├── libphrases-a7448e02a0468eaa.rlib
└── native
src/lib.rs是我们包装箱的根,它看起来像这样
mod english;
mod japanese;
src/english/mod.rs和src/japanese/mod.rs都看起来像这样:
mod greetings;
mod farewells;
在src/english/greetings.rs添加如下:
fn hello() -> String {
"Hello!".to_string()
}
导入外部包装箱
extern crate phrases;
到处公用接口
Rust允许你严格的控制你的接口哪部分是公有的,所以它们默认都是私有的。你需要使用pub关键字,来公开它。
在我们的src/lib.rs,让我们给english模块声明添加一个pub:
pub mod english;
mod japanese;
然后在我们的src/english/mod.rs中,加上两个pub:
pub mod greetings;
pub mod farewells;
在我们的src/english/greetings.rs中,让我们在fn声明中加上pub:
pub fn hello() -> String {
"Hello!".to_string()
}
用use导入包装箱
使用use之前我们需要:
extern crate phrases;
fn main() {
println!("Hello in English: {}", phrases::english::greetings::hello());
println!("Goodbye in English: {}", phrases::english::farewells::goodbye());
}
通过use我们可以简化代码
extern crate phrases;
use phrases::english::greetings;
use phrases::english::farewells;
fn main() {
println!("Hello in English: {}", greetings::hello());
println!("Goodbye in English: {}", farewells::goodbye());
}
导入模块而不是直接导入函数被认为是一个最佳实践
public use重导出
修改你的src/japanese/mod.rs为这样:
pub use self::greetings::hello;
pub use self::farewells::goodbye;
mod greetings;
mod farewells;
然后我们可以将引用该包装箱的代码修改为如下
extern crate phrases;
use phrases::english::{greetings,farewells};
use phrases::japanese;
fn main() {
println!("Hello in English: {}", greetings::hello());
println!("Goodbye in English: {}", farewells::goodbye());
println!("Hello in Japanese: {}", japanese::hello());
println!("Goodbye in Japanese: {}", japanese::goodbye());
}
pub use声明将这些函数导入到了我们模块结构空间中。因为我们在japanese模块内使用了pub use,我们现在有了phrases::japanese::hello()和phrases::japanese::goodbye()函数,即使它们的代码在phrases::japanese::greetings::hello()和phrases::japanese::farewells::goodbye()函数中。内部结构并不反映外部接口。
复杂的导入
extern crate phrases as sayings;
use sayings::japanese::greetings as ja_greetings;
use sayings::japanese::farewells::*;
use sayings::english::{self, greetings as en_greetings, farewells as en_farewells};
fn main() {
println!("Hello in English; {}", en_greetings::hello());
println!("And in Japanese: {}", ja_greetings::hello());
println!("Goodbye in English: {}", english::farewells::goodbye());
println!("Again: {}", en_farewells::goodbye());
println!("And in Japanese: {}", goodbye());
}
const
常量贯穿于整个程序的生命周期。更具体的,Rust中的常量并没有固定的内存地址。这是因为实际上它们会被内联到用到它们的地方。为此对同一常量的引用并不能保证引用到相同的内存地址。
const N: i32 = 5;
static
Rust以静态量的方式提供了类似“全局变量”的功能。它们与常量类似,不过静态量在使用时并不内联。这意味着对每一个值只有一个实例,并且位于内存中的固定位置。
static N: i32 = 5;
可变性
static mut N: i32 = 5;
因为这是可变的,一个线程可能在更新N同时另一个在读取它,导致内存不安全。因此访问和改变一个static mut是不安全(unsafe)的,因此必须在unsafe块中操作:
# static mut N: i32 = 5;
unsafe {
N += 1;
println!("N: {}", N);
}
更进一步,任何存储在static的类型必须实现Sync。
初始化
const和static都要求赋予它们一个值。它们只能被赋予一个常量表达式的值。换句话说,你不能用一个函数调用的返回值或任何相似的复合值或在运行时赋值。
#[foo]struct Foo;
mod bar {
#![bar]
}
'''#[foo]作用于下一个项,在这就是struct声明。#![bar]作用于包含它的项,在这是mod声明。否则,它们是一样的。它们都以某种方式改变它们附加到的项的意义。'''
type关键字让你定义另一个类型的别名:
type Name = String;
然而要注意的是,这一个别名,完全不是一个新的类型。换句话说,因为Rust是强类型的,你可以预期两个不同类型的比较会失败:
type Num = i32;
let x: i32 = 5;
let y: Num = 5;
if x == y {
// ...
}
这个比较不会失败
提供了两种不同的在不同类型间转换的方式。第一个,as,用于安全转换。相反,transmute允许任意的转换,而这是 Rust 中最危险的功能之一!
定义关联类型
trait Graph {
type N;
type E;
fn has_edge(&self, &Self::N, &Self::N) -> bool;
fn edges(&self, &Self::N) -> Vec;
}
十分简单。关联类型使用type关键字,并出现在trait体和函数中
实现关联类型
# trait Graph {
# type N;
# type E;
# fn has_edge(&self, &Self::N, &Self::N) -> bool;
# fn edges(&self, &Self::N) -> Vec;
# }
struct Node;
struct Edge;
struct MyGraph;
impl Graph for MyGraph {
type N = Node;
type E = Edge;
fn has_edge(&self, n1: &Node, n2: &Node) -> bool {
true
}
fn edges(&self, n: &Node) -> Vec {
Vec::new()
}
}
trait对象和关联类型
# trait Graph {
# type N;
# type E;
# fn has_edge(&self, &Self::N, &Self::N) -> bool;
# fn edges(&self, &Self::N) -> Vec;
# }
# struct Node;
# struct Edge;
# struct MyGraph;
# impl Graph for MyGraph {
# type N = Node;
# type E = Edge;
# fn has_edge(&self, n1: &Node, n2: &Node) -> bool {
# true
# }
# fn edges(&self, n: &Node) -> Vec {
# Vec::new()
# }
# }
let graph = MyGraph;
let obj = Box::new(graph) as Box>;
限制
我们只能通过指针操作一个不定长类型的实例。&[T]刚好能正常工作,不过[T]不行。一个&[T]能正常工作,不过一个[T]不行。
变量和参数不能拥有动态大小类型。
只有一个struct的最后一个字段可能拥有一个动态大小类型;其它字段则不可以拥有动态大小类型。枚举变量不可以用动态大小类型作为数据。
如果你想要写一个接受动态大小类型的函数,你可以使用这个特殊的限制,?Sized:
struct Foo {
f: T,
}
Rust 允许有限形式的运算符重载。特定的运算符可以被重载。要支持一个类型间特定的运算符,你可以实现一个的特定的重载运算符的trait。
use std::ops::Add;
#[derive(Debug)]struct Point {
x: i32,
y: i32,
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point { x: self.x + other.x, y: self.y + other.y }
}
}
fn main() {
let p1 = Point { x: 1, y: 0 };
let p2 = Point { x: 2, y: 3 };
let p3 = p1 + p2;
println!("{:?}", p3);
}
有一系列可以这样被重载的运算符,并且所有与之相关的trait都在std::ops
模块中。查看它的文档来获取完整的列表
在泛型结构体中使用运算符trait
use std::ops::Mul;
trait HasArea {
fn area(&self) -> T;
}
struct Square {
x: T,
y: T,
side: T,
}
impl HasArea for Square
where T: Mul
fn area(&self) -> T {
self.side * self.side
}
}
fn main() {
let s = Square {
x: 0.0f64,
y: 0.0f64,
side: 12.0f64,
};
println!("Area of s: {}", s.area());
}
标准库提供了一个特殊的特性,Deref
。它一般用来重载*,解引用运算符:
use std::ops::Deref;
struct DerefExample {
value: T,
}
impl Deref for DerefExample {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}
fn main() {
let x = DerefExample { value: 'a' };
assert_eq!('a', *x);
}
这对编写自定义指针类型很有用。然而,有一个与Deref相关的语言功能:“解引用强制多态(deref coercions)”。规则如下:如果你有一个U类型,和它的实现Deref,(那么)&U的值将会自动转换为&T。这是一个例子:
fn foo(s: &str) {
// borrow a string for a second
}
// String implements Dereflet owned = "Hello".to_string();
// therefore, this works:
foo(&owned);
在一个值的前面用&号获取它的引用。所以owned是一个String,&owned是一个&String,而因为impl Deref for String,&String将会转换为&str,而它是foo()需要的。
标准库提供的另一个非常通用的实现是:
fn foo(s: &[i32]) {
// borrow a slice for a second
}
// Vec implements Dereflet owned = vec![1, 2, 3];
foo(&owned);
向量可以Deref为一个切片。
一个&&&&&&&&&&&&&&&&Foo类型的值仍然可以调用Foo定义的方法,因为编译器会插入足够多的来使类型正确。而正因为它插入,它用了Deref。
定义宏
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
# fn main() {
# assert_eq!(vec![1,2,3], [1, 2, 3]);
# }
这就像一个match表达式分支,不过匹配发生在编译时Rust的语法树中。最后一个分支(这里只有一个分支)的分号是可选的。=>左侧的“模式”叫匹配器(matcher)。它有自己的语法。
匹配器将会匹配任何表达式,把它的语法树绑定到元变量x:expr匹配器将会匹配任何Rust表达式,把它的语法树绑定到元变量x上。expr标识符是一个片段分类符(fragment specifier)。在宏进阶章节(已被本章合并,坐等官方文档更新)中列举了所有可能的分类符。匹配器写在$(...)中,*会匹配0个或多个表达式,表达式之间用逗号分隔。
macro_rules! foo {
() => (let x = 3);
}
fn main() {
foo!();
println!("{}", x);
}
这对let绑定和loop标记有效,对items无效。所以下面的代码可以编译:上面的代码无法编译通过,而下面的则可以
macro_rules! foo {
() => (fn x() { });
}
fn main() {
foo!();
x();
}
Rust 的标准库中有一系列不同的智能指针类型,不过这有两个类型是十分特殊的。Rust的安全大多来源于编译时检查,不过裸指针并没有这样的保证,使用它们是unsafe
的。
const T和mut T在Rust中被称为“裸指针”。
有一些你需要记住的裸指针不同于其它指针的地方。它们是:
不能保证指向有效的内存,甚至不能保证是非空的(不像Box和&);
没有任何自动清除,不像Box,所以需要手动管理资源;
是普通旧式类型,也就是说,它不移动所有权,这又不像Box,因此Rust编译器不能保证不出像释放后使用这种bug;
被认为是可发送的(如果它的内容是可发送的),因此编译器不能提供帮助确保它的使用是线程安全的;例如,你可以从两个线程中并发的访问mut i32而不用同步。
缺少任何形式的生命周期,不像&,因此编译器不能判断出悬垂指针;
除了不允许直接通过const T改变外,没有别名或可变性的保障。
创建一个裸指针是非常安全的:
let x = 5;
let raw = &x as *const i32;
let mut y = 10;
let raw_mut = &mut y as *mut i32;
当你解引用一个裸指针,你要为它并不指向正确的地方负责。为此,你需要unsafe:
let x = 5;
let raw = &x as *const i32;
let points_at = unsafe { *raw };
println!("raw points at {}", points_at);
在运行时,指向一份相同数据的裸指针和引用有相同的表现。事实上,在安全代码中&T引用会隐式的转换为一个const T同时它们的mut变体也有类似的行为(这两种转换都可以显式执行,分别为value as const T和value as mut T)。
反其道而行之,从const到&引用,是不安全的。一个&T总是有效的,所以,最少,const T裸指针必须指向一个T的有效实例。进一步,结果指针必须满足引用的别名和可变性法则。编译器假设这些属性对任何引用都是有效的,不管它们是如何创建的,因而所以任何从裸指针来的转换都断言它们成立。程序员必须保证它。
推荐的转换方法是
// explicit castlet i: u32 = 1;
let p_imm: *const u32 = &i as *const u32;
// implicit coercionlet mut m: u32 = 2;
let p_mut: *mut u32 = &mut m;
unsafe {
let ref_imm: &u32 = &*p_imm;
let ref_mut: &mut u32 = &mut *p_mut;
}
与使用transmute相比更倾向于&*x解引用风格。transmute远比需要的强大,并且(解引用)更受限的操作会更难以错误使用;例如,它要求x是一个指针(不像transmute)。
Rust主要魅力是它强大的静态行为保障。不过安全检查天性保守:有些程序实际上是安全的,不过编译器不能验证它是否是真的。为了写这种类型的程序,我们需要告诉编译器稍微放松它的限制。为此,Rust有一个关键字,unsafe。使用unsafe的代码比正常代码有更少的限制。
unsafe fn danger_will_robinson() {
// scary stuff
}
unsafe {
// scary stuff
}
unsafe trait Scary { }
# unsafe trait Scary { }
unsafe impl Scary for i32 {}
在不安全函数和不安全块,Rust将会让你做3件通常你不能做的事:只有3件。它们是:
访问和更新一个静态可变变量
解引用一个裸指针
调用不安全函数。这是最NB的能力
Box::::new() 堆
test属性
#[test]
执行
cargo test
会运行该属性后面紧跟的函数
ignore属性
#[test]
#[ignore]
只有通过
cargo test -- -- ignore
才会运行ignore属性的函数
tests模块
#[cfg(test)]mod tests {
use super::*;
#[test]fn it_works() {
assert_eq!(4, add_two(2));
}
}
引入了一个cfg属性的mod tests。这个模块允许我们把所有测试集中到一起,并且需要的话还可以定义辅助函数,它们不会成为我们包装箱的一部分。cfg属性只会在我们尝试去运行测试时才会编译测试代码。这样可以节省编译时间,并且也确保我们的测试代码完全不会出现在我们的正式构建中。
tests目录
为了集成测试,我们创建了一个目录tests,并加入lib.rs文件,内容
extern crate adder;
#[test]fn it_works() {
assert_eq!(4, adder::add_two(2));
}
我们现在有一行extern crate adder在开头。这是因为在tests目录中的测试另一个完全不同的包装箱,所以我们需要导入我们的库。
文档测试
这块抽时间再仔细想想
Rust有一个特殊的属性,#[cfg],它允许你基于一个传递给编译器的标记编译代码。它有两种形式:
#[cfg(foo)]
# fn foo() {}
#[cfg(bar = "baz")]
# fn bar() {}
它有一些辅助选项
#[cfg(any(unix, windows))]
# fn foo() {}
#[cfg(all(unix, target_pointer_width = "32"))]
# fn bar() {}
#[cfg(not(foo))]
# fn not_foo() {}
这些选项可以任意嵌套
#[cfg(any(not(unix), all(target_os="macos", target_arch = "powerpc")))]
# fn foo() {}
如果你使用Cargo的话,它们可以在你Cargo.toml中的[features]
部分设置:
[features]# no features by defaultdefault = []# The “secure-password” feature depends on the bcrypt package.secure-password = ["bcrypt"]
他等同于Cargo传递给rustc一个标记:
--cfg feature="${feature_name}"
cfg_attr
#[cfg_attr(a, b)]
# fn foo() {}
如果a通过cfg属性设置了的话这与#[b]相同,否则不起作用。
cfg!
if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
println!("Think Different!");
}
可以在代码中使用这类标记
rustdoc
/// #
“#”后面的内容在通过注释生成的测试代码中会使用,但是生成的注释文档中不会显示
迭代器,迭代适配器,消费者
消费者
消费者 操作一个迭代器,返回一些值或者几种类型的值。最常见的消费者是collect()
常见的消费者
let one_to_one_hundred = (1..101).collect::>();
let greater_than_forty_two = (0..100)
.find(|x| *x > 42);
let sum = (1..4).fold(0, |sum, x| sum + x);
迭代器
迭代器是惰性的(lazy )并且不需要预先生成所有的值
范围是你会见到的两个基本迭代器之一。另一个是iter()。iter()可以把一个向量转换为一个简单的按顺序给出每个值的迭代器:
let nums = vec![1, 2, 3];
for num in nums.iter() {
println!("{}", num);
}
迭代适配器
代适配器(Iterator adapters)获取一个迭代器然后按某种方法修改它,并产生一个新的迭代器。
(1..100).map(|x| x + 1);
在其他迭代器上调用map,然后产生一个新的迭代器,它的每个元素引用被调用了作为参数的闭包。
因为迭代器是惰性的,所以下面代码不会打印任何内容
(1..100).map(|x| println!("{}", x));
有大量有趣的迭代适配器。take(n)会返回一个源迭代器下n个元素的新迭代器
filter()是一个带有一个闭包参数的适配器。这个闭包返回true或false。filter()返回的新迭代器只包含闭包返回true的元素:
for i in (1..100).filter(|&x| x % 2 == 0) {
println!("{}", i);
}
链式结构
(1..)
.filter(|&x| x % 2 == 0)
.filter(|&x| x % 3 == 0)
.take(5)
.collect::>();
send
当一个T类型实现了Send,它向编译器指示这个类型的所有权可以在线程间安全的转移。
我们有一个连接两个线程的通道,我们想要能够向通道发送些数据到另一个线程。因此,我们要确保这个类型实现了Send。
sync
当一个类型T实现了Sync,它向编译器指示这个类型在多线程并发时没有导致内存不安全的可能性。这隐含了没有内部可变性的类型天生是Sync的,这包含了基本类型(如 u8)和包含他们的聚合类型。
为了在线程间共享引用,Rust 提供了一个叫做Arc的 wrapper 类型。Arc实现了Send和Sync当且仅当T实现了Send和Sync。例如,一个Arc
类型的对象不能在线程间传送因为RefCell并没有实现Sync,因此Arc
并不会实现Send。
这两个特性允许你使用类型系统来确保你代码在并发环境的特性。
线程
thread::spawn()方法接受一个闭包,它将会在一个新线程中执行。它返回一线程的句柄,这个句柄可以用来等待子线程结束并提取它的结果
use std::thread;
fn main() {
let handle = thread::spawn(|| {
"Hello from a thread!"
});
println!("{}", handle.join().unwrap());
}
安全共享可变状态
# use std::sync::{Arc, Mutex};
# use std::thread;
# use std::time::Duration;
# fn main() {
# let data = Arc::new(Mutex::new(vec![1, 2, 3]));
# for i in 0..3 {
# let data = data.clone();
thread::spawn(move || {
let mut data = data.lock().unwrap();
data[i] += 1;
});
# }
# thread::sleep(Duration::from_millis(50));
# }
首先,我们调用lock(),它获取了互斥锁。因为这可能失败,它返回一个Result,并且因为这仅仅是一个例子,我们unwrap()结果来获得一个数据的引用。现实中的代码在这里应该有更健壮的错误处理。下面我们可以随意修改它,因为我们持有锁
通道
use std::thread;
use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
for i in 0..10 {
let tx = tx.clone();
thread::spawn(move || {
let answer = i * i;
tx.send(answer).unwrap();
});
}
for _ in 0..10 {
println!("{}", rx.recv().unwrap());
}
}
Panics
use std::thread;
let handle = thread::spawn(move || {
panic!("oops!");
});
let result = handle.join();
assert!(result.is_err());
我们的Thread返回一个Result,它允许我们检查我们的线程是否发生panics。
Option, Result
核心是用map组合来减少 case analysis
Box
Box\是一个“自我拥有的”,或者“装箱”的指针。因为它可以维持引用和包含的数据,它是数据的唯一的拥有者。特别的,当执行类似如下代码时:
let x = Box::new(1);
let y = x;
// x no longer accessible here
这里,装箱被移动进了y。因为x不再拥有它,此后编译器不再允许程序猿使用x。
当一个装箱(还没有被移动的)离开了作用域,析构函数将会运行。这个析构函数负责释放内部的数据。
&T和&mut T
这分别是不可变和可变引用。他们遵循“读写锁”的模式,也就是你只可能拥有一个数据的可变引用,或者任意数量的不可变引用,但不是两者都有。
这些指针不能在超出他们的生命周期的情况下被拷贝。
** const T和mut T**
这些是C风格的指针,并没附加生命周期或所有权。他们只是指向一些内存位置,没有其他的限制。他们能提供的唯一的保证是除非在标记为unsafe的代码中他们不会被解引用。
他们在构建像Vec这样的安全,低开销抽象时是有用的,不过应该避免在安全代码中使用。
Rc
Rc\是一个引用计数指针。换句话说,这让我们拥有相同数据的多个“有所有权”的指针,并且数据在所有指针离开作用域后将被释放(析构函数将会执行)。
在内部,它包含一个共享的“引用计数”(也叫做“refcount”),每次Rc被拷贝时递增,而每次Rc离开作用域时递减。Rc的主要职责是确保共享的数据的析构函数被调用。
这里内部的数据是不可变的,并且如果创建了一个循环引用,数据将会泄露。如果我们想要数据在存在循环引用时不被泄漏,我们需要一个垃圾回收器。
保证
这里(Rc)提供的主要保证是,直到所有引用离开作用域后,相关数据才会被销毁。
当我们想要动态分配并在程序的不同部分共享一些(只读)数据,且不确定哪部分程序会最后使用这个指针时,我们应该用Rc。当&T不可能静态地检查正确性,或者程序员不想浪费时间编写反人类的代码时,它可以作为&T的可行的替代。
这个指针并不是线程安全的,并且Rust也不会允许它被传递或共享给别的线程。这允许你在不必要的情况下的原子性开销。
开销
多分配字段用来存放引用计数,拷贝时不是深拷贝,只增加引用计数,离开作用域时减引用数
Cell提供内部可变性。换句话说,他们包含的数据可以被修改,即便是这个类型并不能以可变形式获取(例如,当他们位于一个&指针或Rc之后时)。
Cell
use std::cell::Cell;
let x = Cell::new(1);
let y = &x;
let z = &x;
x.set(2);
y.set(3);
z.set(4);
println!("{}", x.get());
let mut x = 1;
let y = &mut x;
let z = &mut x;
x = 2;
*y = 3;
*z = 4;
println!("{}", x);
上面两端代码要实现的功能是一样的,但是只有上面的可以编译成功
开销
使用Cell并没有运行时开销,不过你使用它来封装一个很大的(Copy)结构体,可能更适合封装单独的字段为Cell因为每次写入都会是一个结构体的完整拷贝。
RefCell
RefCell\也提供了内部可变性,不过并不限制为Copy类型。
相对的,它有运行时开销。RefCell在运行时使用了读写锁模式,不像&T/&mut T那样在编译时执行。这通过borrow()和borrow_mut()函数来实现,它修改一个内部引用计数并分别返回可以不可变的和可变的解引用的智能指针。当智能指针离开作用域引用计数将被恢复。通过这个系统,我们可以动态的确保当有一个有效的可变借用时绝不会有任何其他有效的借用。如果程序猿尝试创建一个这样的借用,线程将会恐慌。
use std::cell::RefCell;
let x = RefCell::new(vec![1,2,3,4]);
{
println!("{:?}", *x.borrow())
}
{
let mut my_ref = x.borrow_mut();
my_ref.push(1);
}
与Cell相似,它主要用于难以或不可能满足借用检查的情况。大体上我们知道这样的改变不会发生在一个嵌套的形式中,不过检查一下是有好处的。
对于大型的,复杂的程序,把一些东西放入RefCell来将事情变简单是有用的。例如,Rust编译器内部的ctxt
结构体中的很多map都在这个封装中。他们只会在创建时被修改一次(但并不是正好在初始化后),或者在明显分开的地方多次多次修改。然而,因为这个结构体被广泛的用于各个地方,有效的组织可变和不可变的指针将会是困难的(也许是不可能的),并且可能产生大量的难以扩展的&指针。换句话说,RefCell提供了一个廉价(并不是零开销)的方式来访问它。之后,如果有人增加一些代码来尝试修改一个已经被借用的cell时,这将会产生(通常是决定性的)一个恐慌,并会被追溯到那个可恶的借用上。
相似的,在Servo的DOM中有很多可变量,大部分对于一个DOM类型都是本地的,不过有一些交错在DOM中并修改了很多内容。使用RefCell和Cell来保护所有的变化可以让我们免于担心到处都是的可变性,并且同时也表明了何处正在发生变化。
注意如果是一个能用&指针的非常简单的情形应该避免使用RefCell。
注意非线程安全的类型不能在线程间传递,并且这是在编译时检查的。
Arc
Arc\就是一个使用原子引用计数版本的Rc(Atomic reference count,因此是“Arc”)。它可以在线程间自由的传递。
C++的shared_ptr与Arc类似,然而C++的情况中它的内部数据总是可以改变的。为了语义上与C++的形式相似,我们应该使用Arc
保证
类似Rc,它提供了当最后的Arc离开作用域时(不包含任何的循环引用)其内部数据的析构函数将被执行的(线程安全的)保证。
Mutex和RwLock
Mutex\和RwLock\通过RAII guard(guard是一类直到析构函数被调用时能保持一些状态的对象)提供了互斥功能。对于这两个类型,mutex直到我们调用lock()之前它都是无效的,此时直到我们获取锁这个线程都会被阻塞,同时它会返回一个guard。这个guard可以被用来访问它的内部数据(可变的),而当guard离开作用域锁将被释放。
{
let guard = mutex.lock();
// guard dereferences mutably to the inner type
*guard += 1;
} // lock released when destructor runs
RwLock对多线程读有额外的效率优势。只要没有writer,对于共享的数据总是可以安全的拥有多个reader;同时RwLock让reader们获取一个“读取锁”。这样的锁可以并发的获取并通过引用计数记录。writer必须获取一个“写入锁”,它只有在所有reader都离开作用域时才能获取。
保证
这两个类型都提供了线程间安全的共享可变性,然而他们易于产生死锁。一些额外的协议层次的安全性可以通过类型系统获取。
开销
他们在内部使用类原子类型来维持锁,这样的开销非常大(他们可以阻塞处理器所有的内存读取知道他们执行完毕)。而当有很多并发访问时等待这些锁也将是很慢的。
对于多数类型,当你想要获取一个自我拥有或借用的类型,&T就足够了。不过当有多于一种借用的值时,Borrow就能起作用了。引用和slice就是一个能体现这一点的地方:你可以有&[T]或者&mut [T]。如果我们想接受这两种类型,Borrow就是你需要的:
use std::borrow::Borrow;
use std::fmt::Display;
fn foo + Display>(a: T) {
println!("a is borrowed: {}", a);
}
let mut i = 5;
foo(&i);
foo(&mut i);
AsRef特性是一个转换特性。它用来在泛型中把一些值转换为引用。像这样:
let s = "Hello".to_string();
fn foo>(s: T) {
let slice = s.as_ref();
}
选择Borrow当你想要抽象不同类型的借用,或者当你创建一个数据结构它把自我拥有和借用的值看作等同的,例如哈希和比较。
选择AsRef当你想要直接把一些值转换为引用,和当你在写泛型代码的时候。
开发版,测试版,稳定版
#![feature(test)]extern crate test;
pub fn add_two(a: i32) -> i32 {
a + 2
}
#[cfg(test)]mod tests {
use super::*;
use test::Bencher;
#[test]fn it_works() {
assert_eq!(4, add_two(2));
}
#[bench]fn bench_add_two(b: &mut Bencher) {
b.iter(|| add_two(2));
}
}
我们导入了testcrate,它包含了对基准测试的支持。我们也定义了一个新函数,带有bench属性。与一般的不带参数的测试不同,基准测试有一个&mut Bencher参数。Bencher提供了一个iter方法,它接收一个闭包。这个闭包包含我们想要测试的代码。
编写基准测试的建议:
把初始代码放于iter循环之外,只把你想要测试的部分放入它
确保每次循环都做了“同样的事情”,不要累加或者改变状态
确保外边的函数也是幂等的(idempotent),基准测试runner可能会多次运行它
确保iter循环内简短而快速,这样基准测试会运行的很快同时校准器可以在合适的分辨率上调整运转周期
确保iter循环执行简单的工作,这样可以帮助我们准确的定位性能优化(或不足)
目前唯一稳定的创建Box的方法是通过Box::new方法。并且不可能在一个模式匹配中稳定的析构一个Box。不稳定的box关键字可以用来创建和析构Box。
#![feature(box_syntax, box_patterns)]fn main() {
let b = Some(box 5);
match b {
Some(box n) if n < 0 => {
println!("Box contains negative number {}", n);
},
Some(box n) if n >= 0 => {
println!("Box contains non-negative number {}", n);
},
None => {
println!("No box");
},
_ => unreachable!()
}
}
注意这些功能目前隐藏在box_syntax(装箱创建)和box_patterns(析构和模式匹配)gate 之后因为它的语法在未来可能会改变。
struct BigStruct {
one: i32,
two: i32,
// etc
one_hundred: i32,
}
fn foo(x: Box) -> Box {
Box::new(*x)
}
fn main() {
let x = Box::new(BigStruct {
one: 1,
two: 2,
one_hundred: 100,
});
let y = foo(x);
}
#![feature(box_syntax)]struct BigStruct {
one: i32,
two: i32,
// etc
one_hundred: i32,
}
fn foo(x: Box) -> BigStruct {
*x
}
fn main() {
let x = Box::new(BigStruct {
one: 1,
two: 2,
one_hundred: 100,
});
let y: Box = box foo(x);
}
这在不牺牲性能的前提下获得了灵活性。
你可能会认为这会给我们带来很差的性能:返回一个值然后马上把它装箱?难道这在哪里不都是最糟的吗?Rust 显得更聪明。这里并没有拷贝。main为装箱分配了足够的空间,向foo传递一个指向他内存的x,然后foo直接向Box中写入数据。
因为这很重要所以要说两遍:返回指针会阻止编译器优化你的代码。允许调用函数选择它们需要如何使用你的输出。
如果你想在一个切片或数组上匹配,你可以通过slice_patterns功能使用&:
#![feature(slice_patterns)]fn main() {
let v = vec!["match_this", "1"];
match &v[..] {
["match_this", second] => println!("The second element is {}", second),
_ => {},
}
}
advanced_slice_patternsgate 让你使用..表明在一个切片的模式匹配中任意数量的元素。这个通配符对一个给定的数组只能只用一次。如果在..之前有一个标识符,结果会被绑定到那个名字上。例如:
#![feature(advanced_slice_patterns, slice_patterns)]fn is_symmetric(list: &[u32]) -> bool {
match list {
[] | [_] => true,
[x, inside.., y] if x == y => is_symmetric(inside),
_ => false
}
}
fn main() {
let sym = &[0, 1, 4, 2, 4, 1, 0];
assert!(is_symmetric(sym));
let not_sym = &[0, 1, 7, 2, 4, 1, 0];
assert!(!is_symmetric(not_sym));
}
通过associated_consts功能,你像这样可以定义常量:
#![feature(associated_consts)]trait Foo {
const ID: i32;
}
impl Foo for i32 {
const ID: i32 = 1;
}
fn main() {
assert_eq!(1, i32::ID);
}
也可以实现一个默认值:
#![feature(associated_consts)]trait Foo {
const ID: i32 = 1;
}
impl Foo for i32 {
}
impl Foo for i64 {
const ID: i32 = 5;
}
fn main() {
assert_eq!(1, i32::ID);
assert_eq!(5, i64::ID);
}
关联常量并不一定要关联在一个 trait 上。一个struct的impl块或enum也行:
#![feature(associated_consts)]struct Foo;
impl Foo {
const FOO: u32 = 3;
}
编译器目前自带两个默认分配器:alloc_system和alloc_jemalloc(然而一些目标平台并没有 jemalloc)。这些分配器是正常的 Rust crate 并包含分配和释放内存的 routine 的实现。标准库并不假设使用任何一个编译,而且编译器会在编译时根据被产生的输出类型决定使用哪个分配器。
编译器产生的二进制文件默认会使用alloc_jemalloc(如果可用的话)。在这种情况下编译器“控制了一切”,从它超过了最终链接的权利的角度来看。大体上这意味着分配器选择可以被交给编译器。
动态和静态库,然而,默认使用alloc_system。这里 Rust 通常是其他程序的“客人”或者处于并没有权决定应使用的分配器的世界。为此它求助于标准 API(例如,malloc和free)来获取和释放内存。
#![feature(alloc_system)]extern crate alloc_system;
fn main() {
let a = Box::new(4); // allocates from the system allocatorprintln!("{}", a);
}
#![feature(alloc_jemalloc)]#![crate_type = "dylib"]extern crate alloc_jemalloc;
pub fn foo() {
let a = Box::new(4); // allocates from jemallocprintln!("{}", a);
}
# fn main() {}
# // only needed for rustdoc --test down below
# #![feature(lang_items)]// The compiler needs to be instructed that this crate is an allocator in order// to realize that when this is linked in another allocator like jemalloc should// not be linked in#![feature(allocator)]#![allocator]// Allocators are not allowed to depend on the standard library which in turn// requires an allocator in order to avoid circular dependencies. This crate,// however, can use all of libcore.#![no_std]// Let's give a unique name to our custom allocator#![crate_name = "my_allocator"]#![crate_type = "rlib"]// Our system allocator will use the in-tree libc crate for FFI bindings. Note// that currently the external (crates.io) libc cannot be used because it links// to the standard library (e.g. `#![no_std]` isn't stable yet), so that's why// this specifically requires the in-tree version.#![feature(libc)]extern crate libc;
// Listed below are the five allocation functions currently required by custom// allocators. Their signatures and symbol names are not currently typechecked// by the compiler, but this is a future extension and are required to match// what is found below.//// Note that the standard `malloc` and `realloc` functions do not provide a way// to communicate alignment so this implementation would need to be improved// with respect to alignment in that aspect.#[no_mangle]pub extern fn __rust_allocate(size: usize, _align: usize) -> *mut u8 {
unsafe { libc::malloc(size as libc::size_t) as *mut u8 }
}
#[no_mangle]pub extern fn __rust_deallocate(ptr: *mut u8, _old_size: usize, _align: usize) {
unsafe { libc::free(ptr as *mut libc::c_void) }
}
#[no_mangle]pub extern fn __rust_reallocate(ptr: *mut u8, _old_size: usize, size: usize,
_align: usize) -> *mut u8 {
unsafe {
libc::realloc(ptr as *mut libc::c_void, size as libc::size_t) as *mut u8
}
}
#[no_mangle]pub extern fn __rust_reallocate_inplace(_ptr: *mut u8, old_size: usize,
_size: usize, _align: usize) -> usize {
old_size // this api is not supported by libc
}
#[no_mangle]pub extern fn __rust_usable_size(size: usize, _align: usize) -> usize {
size
}
# // just needed to get rustdoc to test this
# fn main() {}
# #[lang = "panic_fmt"] fn panic_fmt() {}
# #[lang = "eh_personality"] fn eh_personality() {}
# #[lang = "eh_unwind_resume"] extern fn eh_unwind_resume() {}
# #[no_mangle] pub extern fn rust_eh_register_frames () {}
# #[no_mangle] pub extern fn rust_eh_unregister_frames () {}
extern crate my_allocator;
fn main() {
let a = Box::new(8); // allocates memory via our custom allocator crateprintln!("{}", a);
}