Rust学习(通过例子学习Rust)(3)

Rust学习(通过例子学习Rust)(3)

十五. 作用域规则

15.1 RALL

Rust 的变量不只是在栈中保存数据:它们也占有资源,比如 Box 占有堆(heap)中的内存。Rust 强制实行 RAII(Resource Acquisition Is Initialization,资源获取即初始化),所以任何对象在离开作用域时,它的析构函数(destructor)就被调用,然后它占有的资源就被释放。

15.2 所有权和移动

因为变量要负责释放它们拥有的资源,所以资源只能拥有一个所有者。这也防止了资源的重复释放。注意并非所有变量都拥有资源(例如引用)。
在进行赋值(let x = y)或通过值来传递函数参数(foo(x))的时候,资源的所有权(ownership)会发生转移。按照 Rust 的说法,这被称为资源的移动(move)。
当所有权发生转移时,数据的可变性可能发生转变

fn main() {
    let a = Box::new(3);
    println!("{}", a);
    // error
    //*a = 3;
    let mut mut_a = Box::new(3);
    println!("{}", mut_a);//3
    *mut_a = 5;
    println!("{}", mut_a);//5
}

在单个变量的解构内,可以同时使用 by-move 和 by-reference 模式绑定。这样做将导致变量的部分移动(partial move)

fn main() {
    struct Person {
        name: String,
        age: u8,
    }
    let p = Person {
        name: String::from("张三"),
        age: 18,
    };
    let Person { name, ref age } = p;
    println!("name:{} age:{}", name, age);
    //error
    //println!("person:{}",p);
    // `p` 不能使用,但 `p.age` 因为没有被移动而可以继续使用
    println!("age = {}", p.age);
}
15.3 借用

可变数据可以使用 &mut T 进行可变借用。这叫做可变引用(mutable reference),它使借用者可以读/写数据。相反,&T 通过不可变引用(immutable reference)来借用数据,借用者可以读数据而不能更改数据:
数据可以多次不可变借用,但是在不可变借用的同时,原始数据不能使用可变借用。或者说,同一时间内只允许一次可变借用。仅当最后一次使用可变引用之后,原始数据才可以再次借用。
在通过 let 绑定来进行模式匹配或解构时,ref 关键字可用来创建结构体/元组的字段的引用

15.4 生命周期

生命周期(lifetime)是这样一种概念,编译器(中的借用检查器)用它来保证所有的借用都是有效的。

15.4.1 生命周期标注
// `print_refs` 接受两个 `i32` 的引用,它们有不同的生命周期 `'a` 和 `'b`。
// 这两个生命周期都必须至少要和 `print_refs` 函数一样长。
fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) {
    println!("x:{} y:{}", x, y);
}

fn failed_borrow<'a>() {
    let _x = 12;
    // 报错:`_x` 的生命周期不够长
    //let y:&'a i32 = &_x;
}
fn main() {
    let (four, nine) = (4, 9);
    // 两个变量的借用(`&`)都传进函数。
    print_refs(&four, &nine);

    failed_borrow();
    // `failed_borrow` 未包含引用,因此不要求 `'a` 长于函数的生命周期,
    // 但 `'a` 寿命确实更长。因为该生命周期从未被约束,所以默认为 `'static`。
}
15.4.2 函数

任何引用都必须拥有标注好的生命周期。
任何被返回的引用都必须有和某个输入量相同的生命周期或是静态类型(static)。

15.4.3 方法

方法一般是不需要标明生命周期的,因为 self 的生命周期会赋给所有的输出生命周期参数,

//不标注
struct Owner(i32);

impl Owner {
    fn add_one(&mut self) {
        self.0 += 1;
    }
    fn print(&self) {
        println!("{}", self.0);
    }
}
fn main() {
    let mut owner = Owner(1);
    owner.add_one();
    owner.print();//2
}
//标注
struct Owner(i32);

impl Owner {
    fn add_one<'a>(&'a mut self) {
        self.0 += 1;
    }
    fn print<'a>(&'a self) {
        println!("{}", self.0);
    }
}
fn main() {
    let mut owner = Owner(1);
    owner.add_one();
    owner.print(); //2
}
15.4.4 机构体

在结构体中标注生命周期也和函数的类似:

15.4.5 trait

trait 方法中生命期的标注基本上与函数类似。注意,impl 也可能有生命周期的标注。

15.4.6 约束也可以使用

就如泛型类型能够被约束一样,生命周期(它们本身就是泛型)也可以使用约束。: 字符的意义在这里稍微有些不同,不过 + 是相同的。注意下面的说明:

T: 'a:在 T 中的所有引用都必须比生命周期 'a 活得更长。
T: Trait + 'a:T 类型必须实现 Trait trait,并且在 T 中的所有引用都必须比 'a 活得更长。

15.4.7 强制转换

一个较长的生命周期可以强制转换为一个较短的,反之亦然。

// 在这里,Rust 推导了一个尽可能短的生命周期。
// 然后这两个引用都被强制转成这个生命周期。
fn multiply<'a>(first: &'a i32, second: &'a i32) -> i32 {
    first * second
}

// `<'a: 'b, 'b>` 读作生命周期 `'a` 至少和 `'b` 一样长。
// 在这里我们我们接受了一个 `&'a i32` 类型并返回一个 `&'b i32` 类型,这是
// 强制转换得到的结果。
fn choose_first<'a: 'b, 'b>(first: &'a i32, _: &'b i32) -> &'b i32 {
    first
}

fn main() {
    let first = 2; // 较长的生命周期

    {
        let second = 3; // 较短的生命周期

        println!("The product is {}", multiply(&first, &second));
        println!("{} is the first", choose_first(&first, &second));
    };
}

15.4.8 static 的生命周期

'static 生命周期是可能的生命周期中最长的,它会在整个程序运行的时期中存在。'static 生命周期也可被强制转换成一个更短的生命周期。有两种方式使变量拥有 'static 生命周期,它们都把数据保存在可执行文件的只读内存区:

使用 static 声明来产生常量(constant)。
产生一个拥有 &'static str 类型的 string 字面量。

// 产生一个拥有 `'static` 生命周期的常量。
static NUM: i32 = 18;

// 返回一个指向 `NUM` 的引用,该引用不取 `NUM` 的 `'static` 生命周期,
// 而是被强制转换成和输入参数的一样。
fn coerce_static<'a>(_: &'a i32) -> &'a i32 {
    &NUM
}

fn main() {
    {
        // 产生一个 `string` 字面量并打印它:
        let static_string = "I'm in read-only memory";
        println!("static_string: {}", static_string);

        // 当 `static_string` 离开作用域时,该引用不能再使用,不过
        // 数据仍然存在于二进制文件里面。
    }

    {
        // 产生一个整型给 `coerce_static` 使用:
        let lifetime_num = 9;

        // 将对 `NUM` 的引用强制转换成 `lifetime_num` 的生命周期:
        let coerced_static = coerce_static(&lifetime_num);

        println!("coerced_static: {}", coerced_static);//18
    }

    println!("NUM: {} stays accessible!", NUM);//18
}

15.4.9 生命周期省略

有些生命周期的模式太常用了,所以借用检查器将会隐式地添加它们以减少程序输入量和增强可读性。这种隐式添加生命周期的过程称为省略(elision)。在 Rust 使用省略仅仅是因为这些模式太普遍了。

十六 trait

trait 是对未知类型 Self 定义的方法集。该类型也可以访问同一个 trait 中定义的其他方法。

16.1 派生

通过 #[derive] 属性,编译器能够提供某些 trait 的基本实现。如果需要更复杂的行为,这些 trait 也可以手动实现。
下面是可以自动派生的 trait:

比较 trait: Eq, PartialEq, Ord, PartialOrd
Clone, 用来从 &T 创建副本 T。
Copy,使类型具有 “复制语义”(copy semantics)而非 “移动语义”(move semantics)。
Hash,从 &T 计算哈希值(hash)。
Default, 创建数据类型的一个空实例。
Debug,使用 {:?} formatter 来格式化一个值。

16.2 dyn 使用

Rust 编译器需要知道每个函数的返回类型需要多少空间。这意味着所有函数都必须返回一个具体类型。与其他语言不同,如果你有个像 Animal 那样的的 trait,则不能编写返回 Animal 的函数,因为其不同的实现将需要不同的内存量。

struct Sheep {}
struct Cow {}

trait Animal {
    // 实例方法签名
    fn noise(&self) -> &'static str;
}

// 实现 `Sheep` 的 `Animal` trait。
impl Animal for Sheep {
    fn noise(&self) -> &'static str {
        "baaaaah!"
    }
}

// 实现 `Cow` 的 `Animal` trait。
impl Animal for Cow {
    fn noise(&self) -> &'static str {
        "moooooo!"
    }
}

// 返回一些实现 Animal 的结构体,但是在编译时我们不知道哪个结构体。
fn random_animal(random_number: f64) -> Box<dyn Animal> {
    if random_number < 0.5 {
        Box::new(Sheep {})
    } else {
        Box::new(Cow {})
    }
}

fn main() {
    let random_number = 0.234;
    let animal = random_animal(random_number);
    println!("You've randomly chosen an animal, and it says {}", animal.noise());
}
16.3 运算符重载

std::ops::Add trait 用来指明 + 的功能

16.4 drop

Drop trait 只有一个方法:drop,当对象离开作用域时会自动调用该方法。Drop trait 的主要作用是释放实现者的实例拥有的资源

16.5 Iterator trait

Iterator trait 用来对集合(collection)类型(比如数组)实现迭代器。

16.6 impl trait

如果函数返回实现了 MyTrait 的类型,可以将其返回类型编写为 -> impl MyTrait。这可以大大简化你的类型签名!

16.7 Clone

当处理资源时,默认的行为是在赋值或函数调用的同时将它们转移。但是我们有时候也需要把资源复制一份。
使用
#[derive(Clone)]

16.8 父trait

Rust 没有“继承”,但是您可以将一个 trait 定义为另一个 trait 的超集(即父 trait)

trait CompSciStudent: Programmer + Student {
    fn git_username(&self) -> String;
}
16.9 消除重叠trait

由于每个 trait 实现都有自己的 impl 块,使用时标明类型即可

//impl  UsernameWidget fn get 
//impl  AageWidget fn get 
// struct from from 实现了上述两个trait 
let username = <Form as UsernameWidget>::get(&form);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值