涛声依旧在 发表于 2024-5-13 05:59:13

Rust 高级特性

本文有删减,原文链接高级特性。

目次

[*]不安全 Rust

[*]不安全的超能力
[*]解引用裸指针
[*]调用不安全函数或方法

[*]创建不安全代码的安全抽象
[*]使用 extern 函数调用外部代码

[*]访问或修改可变静态变量
[*]实现不安全 trait
[*]访问联合体中的字段
[*]何时使用不安全代码

[*]高级 trait

[*]关联类型在 trait 定义中指定占位符类型
[*]默认泛型类型参数和运算符重载
[*]完全限定语法与消歧义:调用相同名称的方法
[*]父 trait 用于在另一个 trait 中使用某 trait 的功能
[*]newtype 模式用以在外部类型上实现外部 trait

[*]高级类型

[*]为了类型安全和抽象而使用 newtype 模式
[*]类型别名用来创建类型同义词
[*]从不返回的 never type
[*]动态大小类型和 Sized trait

[*]高级函数与闭包

[*]函数指针
[*]返回闭包

[*]宏

[*]宏和函数的区别
[*]使用 macro_rules! 的声明宏用于通用元编程
[*]用于从属性生成代码的过程宏
[*]如何编写自定义 derive 宏
[*]类属性宏
[*]类函数宏


不安全 Rust

Rust 还隐藏有第二种语言,它不会强制执行这类内存安全保证:这被称为 不安全 Rust(unsafe Rust)。
不安全 Rust 之所以存在,是由于静态分析本质上是保守的。可以使用不安全代码告诉编译器,“相信我,我知道我在干什么。”
另一个 Rust 存在不安全一面的原因是:底层计算机硬件固有的不安全性。假如 Rust 不答应进行不安全操作,那么有些使命则根本完成不了。
不安全的超能力

可以通过 unsafe 关键字来切换到不安全 Rust,接着可以开启一个新的存放不安全代码的块。这里有五类可以在不安全 Rust 中进行而不能用于安全 Rust 的操作,它们称之为 “不安全的超能力(unsafe superpowers)” :

[*]解引用裸指针
[*]调用不安全的函数或方法
[*]访问或修改可变静态变量
[*]实现不安全 trait
[*]访问 union 的字段
unsafe 并不会关闭借用检查器或禁用任何其他 Rust 安全检查:假如在不安全代码中使用引用,它仍会被检查。unsafe 关键字只是提供了那五个不会被编译器检查内存安全的功能。
unsafe 不意味着块中的代码就一定是危险的大概一定导致内存安全问题:其意图在于作为程序员会确保 unsafe 块中的代码以有效的方式访问内存。
解引用裸指针

不安全 Rust 有两个被称为 裸指针(raw pointers)的雷同于引用的新类型。和引用一样,裸指针是不可变或可变的,分别写作 *const T 和 *mut T。这里的星号不是解引用运算符;它是类型名称的一部分。
裸指针与引用和智能指针的区别在于:

[*]答应忽略借用规则,可以同时拥有不可变和可变的指针,或多个指向相同位置的可变指针
[*]不保证指向有效的内存
[*]答应为空
[*]不能实现任何主动清理功能
通过去掉 Rust 强加的保证,可以放弃安全保证以调换性能或使用另一个语言或硬件接口的能力,此时 Rust 的保证并不实用。
通过引用创建裸指针:
let mut num = 5;

let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;注:可以在安全代码中 创建 裸指针,只是不能在不安全块之外 解引用 裸指针。
创建指向任意内存地址的裸指针:
let address = 0x012345usize;
let r = address as *const i32;在 unsafe 块中解引用裸指针:
let mut num = 5;

let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;unsafe {    println!("r1 is: {}", *r1);    println!("r2 is: {}", *r2);}为何还要使用裸指针呢?一个主要的应用场景便是调用 C 代码接口,另一个场景是构建借用检查器无法理解的安全抽象。
调用不安全函数或方法

不安全函数和方法与通例函数方法十分雷同,除了其开头有一个额外的 unsafe。
一个没有做任何操作的不安全函数 dangerous 的例子:
unsafe fn dangerous() {}

unsafe {
    dangerous();
}必须在一个单独的 unsafe 块中调用 dangerous 函数,否则会得到一个错误。不安全函数体也是有效的 unsafe 块,所以在不安全函数中进行另一个不安全操作时无需新增额外的 unsafe 块。
创建不安全代码的安全抽象

函数包含不安全代码并不意味着整个函数都必要标记为不安全的,如标准库中的函数 split_at_mut:它获取一个 slice 并从给定的索引参数开始将其分为两个 slice,用法如下:
let mut v = vec!;

let r = &mut v[..];

let (a, b) = r.split_at_mut(3);

assert_eq!(a, &mut );
assert_eq!(b, &mut );尝试只使用安全 Rust 来实现 split_at_mut:
//这段代码无法通过编译!
fn split_at_mut(values: &mut , mid: usize) -> (&mut , &mut ) {
    let len = values.len();

    assert!(mid <= len);

    (&mut values[..mid], &mut values)
}"C" 部分定义了外部函数所使用的 应用二进制接口(application binary interface,ABI) —— ABI 定义了如安在汇编语言层面调用此函数。
也可以使用 extern 来创建一个答应其他语言调用 Rust 函数的接口,在 fn 关键字之前增加 extern 关键字并为相关函数指定所用到的 ABI,还需增加 # 注解来告诉 Rust 编译器不要 mangle 此函数的名称。
一旦其编译为动态库并从 C 语言中链接,call_from_c 函数就可以或许在 C 代码中访问:
use std::slice;

fn split_at_mut(values: &mut , mid: usize) -> (&mut , &mut ) {
    let len = values.len();
    let ptr = values.as_mut_ptr();

    assert!(mid <= len);

    unsafe {
      (
            slice::from_raw_parts_mut(ptr, mid),
            slice::from_raw_parts_mut(ptr.add(mid), len - mid),
      )
    }
}访问或修改可变静态变量

全局变量在 Rust 中被称为 静态(static)变量,一个拥有字符串 slice 值的静态变量的声明和应用:
use std::slice;

let address = 0x01234usize;
let r = address as *mut i32;

let values: & = unsafe { slice::from_raw_parts_mut(r,10000) };通常静态变量的名称采用 SCREAMING_SNAKE_CASE 写法,静态变量只能储存拥有 'static 生命周期的引用,这意味着 Rust 编译器可以自己计算出其生命周期而无需显式标注,访问不可变静态变量是安全的。
常量与不可变静态变量的区别:

[*]静态变量中的值有一个固定的内存地址,使用这个值总是会访问相同的地址,常量则答应在任何被用到的时候复制其数据。
[*]静态变量可以是可变的,访问和修改可变静态变量都是 不安全 的。
读取或修改一个可变静态变量是不安全的:
extern "C" {
    //希望能够调用的另一个语言中的外部函数的签名和名称
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
      println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}使用 mut 关键来指定可变性,任何读写 COUNTER 的代码都必须位于 unsafe 块中。
实现不安全 trait

当 trait 中至少有一个方法中包含编译器无法验证的稳固式(invariant)时 trait 是不安全的:
//extern 的使用无需 unsafe
#
pub extern "C" fn call_from_c() {
    println!("Just called a Rust function from C!");
}访问联合体中的字段

union 和 struct 雷同,但是在一个实例中同时只能使用一个声明的字段。联合体主要用于和 C 代码中的联合体交互。访问联合体的字段是不安全的,由于 Rust 无法保证当前存储在联合体实例中数据的类型。
何时使用不安全代码

当有来由使用 unsafe 代码时,是可以这么做的,通过使用显式的 unsafe 标注可以更容易地在错误发生时追踪问题的源头。
高级 trait

关联类型在 trait 定义中指定占位符类型

关联类型(associated types)是一个将类型占位符与 trait 相关联的方式,这样 trait 的方法签名中就可以使用这些占位符类型,trait 的实现者会针对特定的实现在这个占位符类型指定相应的具体类型。
Iterator trait 的定义中带有关联类型 Item,它用来替代遍历的值的类型:
static HELLO_WORLD: &str = "Hello, world!";

fn main() {
    println!("name is: {}", HELLO_WORLD);
}关联类型看起来像一个雷同泛型的概念,由于它答应定义一个函数而不指定其可以处理的类型。
在一个 Counter 结构体上实现 Iterator trait ,指定了 Item 的类型为 u32:
static mut COUNTER: u32 = 0;

fn add_to_count(inc: u32) {
    unsafe {
      COUNTER += inc;
    }
}

fn main() {
    add_to_count(3);

    unsafe {
      println!("COUNTER: {}", COUNTER);
    }
}一个使用泛型的 Iterator trait 假想定义:
//在 trait 之前增加 unsafe 关键字将 trait 声明为 unsafe
unsafe trait Foo {
    // methods go here
}

// trait 的实现也必须标记为 unsafe
unsafe impl Foo for i32 {
    // method implementations go here
}

fn main() {}假如使用泛型就可以有多个 Counter 的 Iterator 的实现,当使用 Counter 的 next 方法时,必须提供类型注解来表明希望使用 Iterator 的哪一个实现。
通过关联类型,则无需标注类型,由于不能多次实现这个 trait。当调用 Counter 的 next 时不必每次指定 u32 值的迭代器。
关联类型也会成为 trait 契约的一部分:trait 的实现必须提供一个类型来替代关联类型占位符。
默认泛型类型参数和运算符重载

当使用泛型类型参数时,可以为泛型指定一个默认的具体类型,为泛型类型指定默认类型的语法是在声明泛型类型时使用 。
Rust 并不答应创建自定义运算符或重载任意运算符,不过 std::ops 中所列出的运算符和相应的 trait 可以通过实现运算符相关 trait 来重载。
实现 Add trait 重载 Point 实例的 + 运算符:
pub trait Iterator {
    //占位符类型,trait 的实现者会指定 Item 的具体类型
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}默认泛型类型位于 Add trait 中,一个带有一个方法和一个关联类型的 trait:
impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
      // --snip--尖括号中的 Rhs=Self 语法叫做 默认类型参数(default type parameters),Rhs 是一个泛型类型参数(“right hand side” 的缩写),它用于定义 add 方法中的 rhs 参数。
在 Millimeters 上实现 Add,以便可以或许将 Millimeters 与 Meters 相加:
pub trait Iterator<T> {
    fn next(&mut self) -> Option<T>;
}默认参数类型主要用于如下两个方面:

[*]扩展类型而不粉碎现有代码。
[*]在大部分用户都不必要的特定情况进行自定义。
第一个目的是相似的,但过程是反过来的:假如必要为现有 trait 增加类型参数,为其提供一个默认类型将答应在不粉碎现有实当代码的基础上扩展 trait 的功能。
完全限定语法与消歧义:调用相同名称的方法

Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法,也不能阻止为同一类型同时实现这两个 trait。
两个 trait 定义为拥有 fly 方法,并在直接定义有 fly 方法的 Human 类型上实现这两个 trait:
use std::ops::Add;

#
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() {
    assert_eq!(
      Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
      Point { x: 3, y: 3 }
    );
}当调用 Human 实例的 fly 时,编译器默认调用直接实现在类型上的方法:
trait Add<Rhs=Self> {
    type Output;

    fn add(self, rhs: Rhs) -> Self::Output;
}指定希望调用哪一个 trait 的 fly 方法:
use std::ops::Add;

struct Millimeters(u32);
struct Meters(u32);

//指定 impl Add<Meters> 来设定 Rhs 类型参数的值而不是使用默认的 Self
impl Add<Meters> for Millimeters {
    type Output = Millimeters;

    fn add(self, other: Meters) -> Millimeters {
      Millimeters(self.0 + (other.0 * 1000))
    }
}运行这段代码会打印出:
trait Pilot {
    fn fly(&self);
}

trait Wizard {
    fn fly(&self);
}

struct Human;

impl Pilot for Human {
    fn fly(&self) {
      println!("This is your captain speaking.");
    }
}

impl Wizard for Human {
    fn fly(&self) {
      println!("Up!");
    }
}

impl Human {
    fn fly(&self) {
      println!("*waving arms furiously*");
    }
}不是方法的关联函数没有 self 参数,当存在多个类型大概 trait 定义了相同函数名的非方法函数时,Rust 无法计算出期望的类型,除非使用 完全限定语法(fully qualified syntax)。
一个带有关联函数的 trait 和一个带有同名关联函数并实现了此 trait 的类型:
fn main() {
    let person = Human;
    person.fly();
}
//会打印出 *waving arms furiously*尝试调用 Animal trait 的 baby_name 函数,不过 Rust 并不知道该使用哪一个实现:
fn main() {
    let person = Human;
    Pilot::fly(&person);
    Wizard::fly(&person);
    person.fly();
}由于 Animal::baby_name 没有 self 参数,同时这可能会有其它类型实现了 Animal trait,Rust 无法计算出所需的是哪一个 Animal::baby_name 实现。
使用完全限定语法来指定希望调用的是 Dog 上 Animal trait 实现中的 baby_name 函数:
This is your captain speaking.
Up!
*waving arms furiously*在尖括号中向 Rust 提供了类型注解,并通过在此函数调用中将 Dog 类型看成 Animal 对待,来指定希望调用的是 Dog 上 Animal trait 实现中的 baby_name 函数。
通常,完全限定语法定义为:
trait Animal {
    fn baby_name() -> String;
}

struct Dog;

impl Dog {
    fn baby_name() -> String {
      String::from("Spot")
    }
}

impl Animal for Dog {
    fn baby_name() -> String {
      String::from("puppy")
    }
}

fn main() {
    println!("A baby dog is called a {}", Dog::baby_name());
}
//会打印出:A baby dog is called a Spot

[*]对于不是方法的关联函数,其没有一个 receiver,故只会有其他参数的列表。
[*]可以选择在任何函数或方法调用处使用完全限定语法。
[*]答应省略任何 Rust 可以或许从程序中的其他信息中计算出的部分。
父 trait 用于在另一个 trait 中使用某 trait 的功能

对于一个实现了第一个 trait 的类型,希望要求这个类型也实现了第二个 trait。如此就可使 trait 定义使用第二个 trait 的关联项,这个所需的 trait 是我们实现的 trait 的 父(超)trait(supertrait)。
创建一个带有 outline_print 方法的 trait OutlinePrint,它会将给定的值格式化为带有星号框。给定一个实现了标准库 Display trait 的并返回 (x, y) 的 Point,当调用以 1 作为 x 和 3 作为 y 的 Point 实例的 outline_print 会表现如下:
//会得到一个编译错误
fn main() {
    println!("A baby dog is called a {}", Animal::baby_name());
}实现 OutlinePrint trait,它要求来自 Display 的功能:
fn main() {
    println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}
//会打印出:A baby dog is called a puppy尝试在一个没有实现 Display 的类型上实现 OutlinePrint :
<Type as Trait>::function(receiver_if_method, next_arg, ...);一旦在 Point 上实现 Display 并满足 OutlinePrint 要求的限定,则能成功编译:
**********
*      *
* (1, 3) *
*      *
**********newtype 模式用以在外部类型上实现外部 trait

孤儿规则(orphan rule):只要 trait 或类型对于当前 crate 是当地的话就可以在此类型上实现该 trait,一个绕开这个限定的方法是使用 newtype 模式(newtype pattern)。
假如想要在 Vec 上实现 Display,而孤儿规则阻止我们直接这么做,由于 Display trait 和 Vec 都定义于我们的 crate 之外。可以创建一个包含 Vec 实例的 Wrapper 结构体,接着可以如列表 19-23 那样在 Wrapper 上实现 Display 并使用 Vec 的值:
创建 Wrapper 类型封装 Vec 以便可以或许实现 Display:
use std::fmt;

//指定了 OutlinePrint 需要 Display trait
//否则会报错:在当前作用域中没有找到用于 &Self 类型的方法 to_string
trait OutlinePrint: fmt::Display {
    fn outline_print(&self) {
      let output = self.to_string();
      let len = output.len();
      println!("{}", "*".repeat(len + 4));
      println!("*{}*", " ".repeat(len + 2));
      println!("* {} *", output);
      println!("*{}*", " ".repeat(len + 2));
      println!("{}", "*".repeat(len + 4));
    }
}此方法的缺点是必须直接在 Wrapper 上实现 Vec 的全部方法,这样才可以代理到self.0 上。假如不希望封装类型拥有全部内部类型的方法,只必要自行实现所需的方法。
高级类型

为了类型安全和抽象而使用 newtype 模式

newtype 模式也可以用于一些其他还未讨论的功能:

[*]静态简直保某值不被混淆,和用来表现一个值的单位,如 Millimeters 和 Meters 结构体。
[*]用于抽象掉一些类型的实现细节,如暴露出与直接使用其内部私有类型时所差别的公有 API
[*]隐藏其内部的泛型类型,如封装了 HashMap 的 People 类型。
类型别名用来创建类型同义词

Rust 提供了声明 类型别名(type alias)的能力,使用 type 关键字来给予现有类型另一个名字:
//这段代码无法通过编译!
struct Point {
    x: i32,
    y: i32,
}

impl OutlinePrint for Point {}类型别名的主要用途是减少重复,例如可能会有这样很长的类型:
use std::fmt;

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
      write!(f, "({}, {})", self.x, self.y)
    }
}引入类型别名 Thunk 来减少重复:
type Thunk = Box
页: [1]
查看完整版本: Rust 高级特性