Node.js 18 更新不再需要 Nodemon

以前开发Node的时候,需要如Nodemon一类的工具启动,监听文件变化自动重新加载源码,现在Node.js 18自带了这一个功能,使用方法:

# 监听一切的入口文件相关模块更改和import的第三方module更改
node --watch server.js

# 指定监听路径,除外的module更改不会触发刷新 
# 只支持 Windows, MacOS
node --watch-path=./src --watch-path=./tests index.js

# 刷新时保持原有log输出
node --watch --watch-preserve-output server.js

2月23日墩台山脊三界碑穿越徒步

从天津蓟县石炮沟村开始,到北京平谷红石门村,共12公里,用时5小时多。路比较好认,沿途都有徒步组织留下的贴纸,而且都是比较明显的路,只有过了三界碑之后有一些小路,上面有很多带刺的植物很扎,不太舒服,其余的路没有难度(只有一处山脊很窄,下面就是深渊需要小心点)。我的脚踝较弱,容易崴脚,大腿筋也不舒服,沿途很多碎石路,脚踩不正,脚踝受力不均,而且容易滑,感恩带了登山杖,否则就很辛苦。可能只有初春适合来,不适合夏天来,沿途没有遮挡会非常晒,但是2月份来又没有什么可看的,植物的枯叶还有没掉光的,有些背阴的地方还有积雪。这里风景一般,加上今天天气不是很好,如果晴朗能见度高可能还好一点。沿途能看到一段长城,但是路线并不会去长城那边。

因为是穿越,所以回程车需要到红石门村接我们,村口的村委会有公共厕所。当时很想喝可乐,但问了一个村民说没有小卖铺。最后小狗长的很有趣🐶

六只脚轨迹

A useful video tutorial about how to setup environment for Vite + React + Typescript + Eslint + Prettier + Vitest and so on

Some bash commands and github link in this video:

npm i -D eslint
npx eslint --init

npx install-peerdeps --dev eslint-config-airbnb
npm install eslint-config-airbnb-typescript
npm i -D prettier eslint-config-prettier eslint-plugin-prettier
# crete cjs file for prettier
npm i -D vitest
npm i -D @testing-library/react @testing-library/jest-dom

https://github.com/CodingGarden/react-ts-starter

近期发现一些值得收藏的网站

这类的网站太多,实在没有机会完整的看,但里面确实有些很好的内容,等到要找的时候找不到又非常可惜,深感个人的收藏夹里需要整理,但又没有找到很好的整理方法,放入收藏夹,肯定过一段时间也是找不到了,希望能够有更好的方式管理这些东西,先存在这看以后还会不会用到吧。

一个介绍各种算法和数据结构在JS里实现的仓库,很有价值:https://github.com/trekhleb/javascript-algorithms

一个自学类网站,可以交互式学习计算机科学课程:https://github.com/freeCodeCamp/freeCodeCamp

开发类面试大全:https://github.com/jwasham/coding-interview-university

各种各样的技术工具,不管有没有用先收起来:https://github.com/trimstray/the-book-of-secret-knowledge#other-cheat-sheets-toc

数据建模和数据分析的平台:https://www.kaggle.com/

用Tauri+React构建桌面应用

Tauri是一个取代Electron的方案,而且比Electron应用方面更广,马上也可以用来创建手机应用,而且架构使用rust作为后端,不嵌入chromium的方式也大大减小app的体积和占用的内存。

简单用一个实例说明如何使用react项目构建一个Tauri App,只需要很短的时间就可以完成此教程。

预先安装

需要先安装好npm、cargo等运行环境,参考 https://tauri.app/zh-cn/v1/guides/getting-started/prerequisites

安装tauri-cli

cargo install tauri-cli

创建react项目

如果已有react项目可以略过这一步

npx create-react-app hello-tauri-react

创建Tauri app

cd hello-tauri-react # 进入react项目的路径中

cargo tauri init

这里会问你6个问题,作为配置写在生成好的tauri.conf.json文件里,结束后会为你创建 src-tauri文件夹

第五个问题问你如何启动前端环境,因为是用CRA创建的项目,因此应该是 npm start

其余问题保持原样就可以

启动测试环境

cargo tauri dev

这里它会先启动react环境,再启动app客户端,当你看到这个界面,就是成功了

客户端界面

你会注意到,浏览器窗口自动弹起来是不必要的,打开tauri.conf.json文件,修改 build -> beforeDevCommand

"beforeDevCommand": "BROWSER=none npm start",

这样下次启动的时候就不会再打开浏览器窗口了

发布打包

cargo tauri build

会报一个错误

`com.tauri.dev` is not allowed as it must be unique across application

就是说app的id标识符不让你用com.tauri.dev这个名字,在tauri.conf.json 中找到 tauri -> bundle -> identifier 随便改成你自己想取的名字,再运行

cargo tauri build

Bingo!现在应该可以发布的app生成好了,我是mac系统,文件生成在 src-tauri/target/release/bundle/ 中 pkg是打包后的,macos里有可运行的app,它还帮你生成了安装包,真的是很愉快的开发体验!

Rust总结:可恢复的错误Result

可恢复的错误 Result

unwrap/expect 如果失败就panic

use std::fs::File;

fn main() {
    // 如果调用这段代码时 hello.txt 文件不存在,那么 unwrap 就将直接 panic
    let f = File::open("hello.txt").unwrap();
}

? 和报错传播

fn open_file() -> Result<File, Box<dyn std::error::Error>> {
    // 如果失败就自动return Err
    let mut f = File::open("hello.txt")?;
    Ok(f)
}

? 不仅仅可以用于 Result 的传播,还能用于 Option 的传播

fn last_char_of_first_line(text: &str) -> Option<char> {
    text.lines().next()?.chars().last()
}

新手用 ? 常会犯的错误

初学者在用 ? 时,老是会犯错,例如写出这样的代码:

fn first(arr: &[i32]) -> Option<&i32> {
   arr.get(0)?
}

切记:? 操作符需要一个变量来承载正确的值,这个函数只会返回 Some(&i32) 或者 None,然而只有错误值能直接返回,正确的值不行

实际上 Rust 还支持另外一种形式的 main 函数

use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
    let f = File::open("hello.txt")?;

    Ok(())
}

例子

1

use std::num::ParseIntError;

fn multiply(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> {
    let n1 = n1_str.parse::<i32>();
    let n2 = n2_str.parse::<i32>();
    Ok(n1.unwrap() * n2.unwrap())
}

fn main() {
    let result = multiply("10", "2");
    assert_eq!(result, Ok(20));

    let result = multiply("4", "2");
    assert_eq!(result.unwrap(), 8);

    println!("Success!")
}

? 改写

use std::num::ParseIntError;

// IMPLEMENT multiply with ?
// DON'T use unwrap here
fn multiply(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> {
    let n1 = n1_str.parse::<i32>()?;
    let n2 = n2_str.parse::<i32>()?;
    Ok(n1 * n2)
}

fn main() {
    assert_eq!(multiply("3", "4").unwrap(), 12);
    println!("Success!")
}

连续?

use std::fs::File;
use std::io::{self, Read};

fn read_file1() -> Result<String, io::Error> {
    let f = File::open("hello.txt");
    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();
    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

fn read_file2() -> Result<String, io::Error> {
    let mut s = String::new();
    // 注意 read_to_string(&mut s)?的?虽然不写也可以通过,但是就错过了可能报的错
    File::open("hello.txt")?.read_to_string(&mut s)?;

    Ok(s)
}

fn main() {
    assert_eq!(read_file1().unwrap_err().to_string(), read_file2().unwrap_err().to_string());
    println!("Success!")
}

mapand_then的使用

use std::num::ParseIntError;

fn add_two(n_str: &str) -> Result<i32, ParseIntError> {
   n_str.parse::<i32>().map(|num| num +2)
}

fn main() {
    assert_eq!(add_two("4").unwrap(), 6);

    println!("Success!")
}
use std::num::ParseIntError;

fn add_two(n_str: &str) -> Result<i32, ParseIntError> {
   n_str.parse::<i32>().and_then(|num| Ok(num +2))
}

fn main() {
    assert_eq!(add_two("4").unwrap(), 6);

    println!("Success!")
}

map/and_then改写

use std::num::ParseIntError;

// With the return type rewritten, we use pattern matching without `unwrap()`.
// But it's so Verbose..
fn multiply(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> {
    match n1_str.parse::<i32>() {
        Ok(n1)  => {
            match n2_str.parse::<i32>() {
                Ok(n2)  => {
                    Ok(n1 * n2)
                },
                Err(e) => Err(e),
            }
        },
        Err(e) => Err(e),
    }
}

// Rewriting `multiply` to make it succinct
// You  MUST USING `and_then` and `map` here
fn multiply1(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> {
    // IMPLEMENT...
    n1_str.parse::<i32>().and_then(|n1| {
        n2_str.parse::<i32>().map(|n2| n1 * n2)
    })
}

fn print(result: Result<i32, ParseIntError>) {
    match result {
        Ok(n)  => println!("n is {}", n),
        Err(e) => println!("Error: {}", e),
    }
}

fn main() {
    // This still presents a reasonable answer.
    let twenty = multiply1("10", "2");
    print(twenty);

    // The following now provides a much more helpful error message.
    let tt = multiply("t", "2");
    print(tt);

    println!("Success!")
}

简化类型

use std::num::ParseIntError;

// Define a generic alias for a `Result` with the error type `ParseIntError`.
type Res<T> = Result<T, ParseIntError>;

// Use the above alias to refer to our specific `Result` type.
fn multiply(first_number_str: &str, second_number_str: &str) -> Res<i32> {
    first_number_str.parse::<i32>().and_then(|first_number| {
        second_number_str.parse::<i32>().map(|second_number| first_number * second_number)
    })
}

// Here, the alias again allows us to save some space.
fn print(result: Res<i32>) {
    match result {
        Ok(n)  => println!("n is {}", n),
        Err(e) => println!("Error: {}", e),
    }
}

fn main() {
    print(multiply("10", "2"));
    print(multiply("t", "2"));
}

main里的return问题

原题如果改成用特征对象就会报错

use std::num::ParseIntError;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let number_str = "10";
    let number = match number_str.parse::<i32>() {
        Ok(number)  => number,
        Err(e) => return Err(e),
    };
    println!("{}", number);
    Ok(())
}

?就成功,这是为什么呢?不是说?是个宏,其实也是return了Err(e)的吗?

use std::num::ParseIntError;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let number_str = "10";
    let number = number_str.parse::<i32>()?;
    println!("{}", number);
    Ok(())
}

Rust总结: 动态数组 Vector

动态数组 Vector

创建

let v: Vec<i32> = Vec::new();

容量

容量 capacity 是已经分配好的内存空间,用于存储未来添加到 Vec 中的元素。而长度 len 则是当前 Vec 中已经存储的元素数量。如果要添加新元素时,长度将要超过已有的容量,那容量会自动进行增长:Rust 会重新分配一块更大的内存空间,然后将之前的 Vec 拷贝过去,因此,这里就会发生新的内存分配( 目前 Rust 的容量调整策略是加倍,例如 2 -> 4 -> 8 ..)。

若这段代码会频繁发生,那频繁的内存分配会大幅影响我们系统的性能,最好的办法就是提前分配好足够的容量,尽量减少内存分配。

如果预先知道要存储的元素个数,可以使用 Vec::with_capacity(capacity) 创建动态数组,这样可以避免因为插入大量新数据导致频繁的内存分配和拷贝,提升性能

fn main() {
    let mut vec = Vec::with_capacity(10);

    assert_eq!(vec.len(), 0);
    assert_eq!(vec.capacity(), 10);

    // 由于提前设置了足够的容量,这里的循环不会造成任何内存分配...
    for i in 0..10 {
        vec.push(i);
    }
    assert_eq!(vec.len(), 10);
    assert_eq!(vec.capacity(), 10);

    // ...但是下面的代码会造成新的内存分配
    vec.push(11);
    assert_eq!(vec.len(), 11);
    assert!(vec.capacity() >= 20);


    // 填写一个合适的值,在 `for` 循环运行的过程中,不会造成任何内存分配
    let mut vec = Vec::with_capacity(100);
    for i in 0..100 {
        vec.push(i);
    }

    assert_eq!(vec.len(), 100);
    assert_eq!(vec.capacity(), 100);

    println!("Success!")
}

vec![] 宏

vec![]vec!()vec!{} 都可以

let v = vec![1, 2, 3];

从数组

let arr: [u8; 3] = [1, 2, 3];
let v = Vec::from(arr);

from/into的用法

fn main() {
    // array -> Vec
    // impl From<[T; N]> for Vec
    let arr = [1, 2, 3];
    let v1 = Vec::from(arr);
    let v2: Vec<i32> = arr.into();

    assert_eq!(v1, v2);


    // String -> Vec
    // impl From<String> for Vec
    let s = "hello".to_string();
    let v1: Vec<u8> = s.into();

    let s = "hello".to_string();
    let v2 = s.into_bytes();
    assert_eq!(v1, v2);

    // impl<'_> From<&'_ str> for Vec
    let s = "hello";
    let v3 = Vec::from(s);
    assert_eq!(v2, v3);

    // 迭代器 Iterators 可以通过 collect 变成 Vec
    let v4: Vec<i32> = [0; 10].into_iter().collect();
    assert_eq!(v4, vec![0; 10]);

    println!("Success!")
 }

用extend

fn main() {
    let mut v1 = Vec::from([1, 2, 4]);
    v1.pop();
    v1.push(3);

    let mut v2 = Vec::new();
    v2.extend(&v1);

    assert_eq!(v1, v2);

    println!("Success!")
}

操作

let mut v = Vec::new();
v.push(1); // 插入

// 读取
let v = vec![1, 2, 3, 4, 5];

let third: &i32 = &v[2];
println!("第三个元素是 {}", third);

match v.get(2) {
    Some(third) => println!("第三个元素是 {}", third),
    None => println!("去你的第三个元素,根本没有!"),
}

// 下标与get的区别
let v = vec![1, 2, 3, 4, 5];

let does_not_exist = &v[100]; // panic!
let does_not_exist = v.get(100); // 返回Option

// 遍历
let v = vec![1, 2, 3];
for i in &v {
    println!("{}", i);
}

let mut v = vec![1, 2, 3];
for i in &mut v {
    *i += 10
}

遍历

fn main() {
    let mut v = Vec::from([1, 2, 3]);
    for i in 0..5 {
        println!("{:?}", v.get(i))
    }

    for i in 0..5 {
        if let Some(x) = v.get(i) {
            v[i] = x + 1
        } else {
            v.push(i + 2)
        }
    }

    assert_eq!(format!("{:?}",v), format!("{:?}", vec![2, 3, 4, 5, 6]));

    println!("Success!")
}


//Another solution

fn main() {
    let mut v = Vec::from([1, 2, 3,4,5]);
    for i in 0..5 {
        println!("{:?}", v[i])
    }

    for i in 0..5 {
       v[i] +=1;
    }

    assert_eq!(v, vec![2, 3, 4, 5, 6]);

    println!("Success!")
}

fn main() {
    let mut v = Vec::from([1, 2, 3]);
    for i in 0..5 {
        println!("{:?}", v.get(i))
    }

    for i in 0..5 {
       match v.get_mut(i) {
           Some(item) => *item += 1,
           None => v.push(i+2)
       }
    }

    assert_eq!(v, vec![2, 3, 4, 5, 6]);

    println!("Success!")
}

切片

与 String 的切片类似, Vec 也可以使用切片。如果说 Vec 是可变的,那它的切片就是不可变或者说只读的,我们可以通过 & 来获取切片。

在 Rust 中,将切片作为参数进行传递是更常见的使用方式,例如当一个函数只需要可读性时,那传递 Vec 或 String 的切片 &[T] / &str 会更加适合。

fn main() {
    let mut v = vec![1, 2, 3];

    let slice1 = &v[..];
    // 越界访问将导致 panic.
    // 修改时必须使用 `v.len`
    let slice2 = &v[0..];

    assert_eq!(slice1, slice2);

    // 切片是只读的 (不能修改长度,但可以对元素修改)
    // 注意:切片和 `&Vec` 是不同的类型,后者仅仅是 `Vec` 的引用,并可以通过解引用直接获取 `Vec`
    let vec_ref: &mut Vec<i32> = &mut v;
    (*vec_ref).push(4);
    let slice3 = &mut v[0..];
    // slice3.push(4);
    slice3[0] = 2;

    assert_eq!(slice3, &[1, 2, 3, 4]);

    println!("Success!")
}

存储不同类型元素

用Option

#[derive(Debug)]
enum IpAddr {
    V4(String),
    V6(String)
}
fn main() {
    let v = vec![
        IpAddr::V4("127.0.0.1".to_string()),
        IpAddr::V6("::1".to_string())
    ];

    for ip in v {
        show_addr(ip)
    }
}

fn show_addr(ip: IpAddr) {
    println!("{:?}",ip);
}

用特征对象

trait IpAddr {
    fn display(&self);
}

struct V4(String);
impl IpAddr for V4 {
    fn display(&self) {
        println!("ipv4: {:?}",self.0)
    }
}
struct V6(String);
impl IpAddr for V6 {
    fn display(&self) {
        println!("ipv6: {:?}",self.0)
    }
}

fn main() {
    let v: Vec<Box<dyn IpAddr>> = vec![
        Box::new(V4("127.0.0.1".to_string())),
        Box::new(V6("::1".to_string())),
    ];

    for ip in v {
        ip.display();
    }
}

Rust 总结:特征对象 Trait Objects

特征对象

特征对象指向实现了特征的类型的实例

可以通过 & 引用或者 Box 智能指针的方式来创建特征对象。

trait Draw {
    fn draw(&self) -> String;
}

impl Draw for u8 {
    fn draw(&self) -> String {
        format!("u8: {}", *self)
    }
}

impl Draw for f64 {
    fn draw(&self) -> String {
        format!("f64: {}", *self)
    }
}

// 若 T 实现了 Draw 特征, 则调用该函数时传入的 Box<T> 可以被隐式转换成函数参数签名中的 Box<dyn Draw>
fn draw1(x: Box<dyn Draw>) {
    // 由于实现了 Deref 特征,Box 智能指针会自动解引用为它所包裹的值,然后调用该值对应的类型上定义的 `draw` 方法
    x.draw();
}

fn draw2(x: &dyn Draw) {
    x.draw();
}

fn main() {
    let x = 1.1f64;
    // do_something(&x);
    let y = 8u8;

    // x 和 y 的类型 T 都实现了 `Draw` 特征,因为 Box<T> 可以在函数调用时隐式地被转换为特征对象 Box<dyn Draw>
    // 基于 x 的值创建一个 Box<f64> 类型的智能指针,指针指向的数据被放置在了堆上
    draw1(Box::new(x));
    // 基于 y 的值创建一个 Box<u8> 类型的智能指针
    draw1(Box::new(y));
    draw2(&x);
    draw2(&y);
}
  • draw1 函数的参数是 Box 形式的特征对象,该特征对象是通过 Box::new(x) 的方式创建的
  • draw2 函数的参数是 &dyn Draw 形式的特征对象,该特征对象是通过 &x 的方式创建的
  • dyn 关键字只用在特征对象的类型声明上,在创建时无需使用 dyn
// 这种写法不会工作!因为最终类型匹配到的是具体类型,而非特征对象
/*
pub struct Screen<T: Draw> {
    pub components: Vec<T>,
}

impl<T> Screen<T>
    where T: Draw {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}
*/

pub struct Screen {
    pub components: Vec<Box<dyn Draw>>,
}

impl Screen {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

pub struct Button {
    pub width: u32,
    pub height: u32,
    pub label: String,
}

impl Draw for Button {
    fn draw(&self) {
        // 绘制按钮的代码
    }
}

struct SelectBox {
    width: u32,
    height: u32,
    options: Vec<String>,
}

impl Draw for SelectBox {
    fn draw(&self) {
        // 绘制SelectBox的代码
    }
}


fn main() {
    let screen = Screen {
        components: vec![
            Box::new(SelectBox {
                width: 75,
                height: 10,
                options: vec![
                    String::from("Yes"),
                    String::from("Maybe"),
                    String::from("No")
                ],
            }),
            Box::new(Button {
                width: 50,
                height: 10,
                label: String::from("OK"),
            }),
        ],
    };

    screen.run();
}

简而言之,当类型 Button 实现了特征 Draw 时,类型 Button 的实例对象 btn 可以当作特征 Draw 的特征对象类型来使用,btn 中保存了作为特征对象的数据指针(指向类型 Button 的实例数据)和行为指针(指向 vtable)。

一定要注意,此时的 btn 是 Draw 的特征对象的实例,而不再是具体类型 Button 的实例,而且 btn 的 vtable 只包含了实现自特征 Draw 的那些方法(比如 draw),因此 btn 只能调用实现于特征 Draw 的 draw 方法,而不能调用类型 Button 本身实现的方法和类型 Button 实现于其他特征的方法。也就是说,btn 是哪个特征对象的实例,它的 vtable 中就包含了该特征的方法。

使用 dyn 返回特征

trait Bird {
    fn quack(&self) -> String;
}

struct Duck;
impl Duck {
    fn swim(&self) {
        println!("Look, the duck is swimming")
    }
}
struct Swan;
impl Swan {
    fn fly(&self) {
        println!("Look, the duck.. oh sorry, the swan is flying")
    }
}

impl Bird for Duck {
    fn quack(&self) -> String{
        "duck duck".to_string()
    }
}

impl Bird for Swan {
    fn quack(&self) -> String{
        "swan swan".to_string()
    }
}

fn main() {
    // 填空
    let duck = Duck {};
    duck.swim();

    let bird = hatch_a_bird(2);
    // 变成鸟儿后,它忘记了如何游,因此以下代码会报错
    // bird.swim();
    // 但它依然可以叫唤
    assert_eq!(bird.quack(), "duck duck");

    let bird = hatch_a_bird(1);
    // 这只鸟儿忘了如何飞翔,因此以下代码会报错
    // bird.fly();
    // 但它也可以叫唤
    assert_eq!(bird.quack(), "swan swan");

    println!("Success!")
}

// 实现以下函数
// fn hatch_a_bird(s: usize) -> &'static dyn Bird {
//     if s == 1 {
//         &Swan {}
//     } else {
//         &Duck {}
//     }
// }

fn hatch_a_bird(s: usize) -> Box<dyn Bird> {
    if s == 1 {
        Box::new(Swan {})
    } else {
        Box::new(Duck {})
    }
}

在数组中使用特征对象

trait Bird {
    fn quack(&self);
}

struct Duck;
impl Duck {
    fn fly(&self) {
        println!("Look, the duck is flying")
    }
}
struct Swan;
impl Swan {
    fn fly(&self) {
        println!("Look, the duck.. oh sorry, the swan is flying")
    }
}

impl Bird for Duck {
    fn quack(&self) {
        println!("{}", "duck duck");
    }
}

impl Bird for Swan {
    fn quack(&self) {
        println!("{}", "swan swan");
    }
}

fn main() {
    // 填空
    // let birds : [&dyn Bird; 2]= [
    //     &Duck{},
    //     &Swan{}
    // ];

    let birds : [Box<dyn Bird>; 2]= [
        Box::new(Duck{}),
        Box::new(Swan{})
    ];

    for bird in birds {
        bird.quack();
        // 当 duck 和 swan 变成 bird 后,它们都忘了如何翱翔于天际,只记得该怎么叫唤了。。
        // 因此,以下代码会报错
        // bird.fly();
    }
}

&dyn and Box

// 填空
trait Draw {
    fn draw(&self) -> String;
}

impl Draw for u8 {
    fn draw(&self) -> String {
        format!("u8: {}", *self)
    }
}

impl Draw for f64 {
    fn draw(&self) -> String {
        format!("f64: {}", *self)
    }
}

fn main() {
    let x = 1.1f64;
    let y = 8u8;

    // draw x
    draw_with_box(Box::new(x));

    // draw y
    draw_with_ref(&y);

    println!("Success!")
}

fn draw_with_box(x: Box<dyn Draw>) {
    x.draw();
}

fn draw_with_ref(x: &dyn Draw) {
    x.draw();
}

静态分发和动态分发Static and Dynamic dispatch

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 static_dispatch(x: impl Foo) {

}

// 通过特征对象实现以下函数
fn dynamic_dispatch(y: &dyn Foo) {

}

fn main() {
    let x = 5u8;
    let y = "Hello".to_string();

    static_dispatch(x);
    dynamic_dispatch(&y);

    println!("Success!")
}

对象安全

一个特征能变成特征对象,首先该特征必须是对象安全的,即该特征的所有方法都必须拥有以下特点:

  • 返回类型不能是 Self.
  • 不能使用泛型参数

第一个是特征约束方式:

trait MyTrait {
    fn f(&self) -> Self;
}

impl MyTrait for u32 {
    fn f(&self) -> Self { 42 }
}

impl MyTrait for String {
    fn f(&self) -> Self { self.clone() }
}

fn my_function(x: impl MyTrait) -> impl MyTrait  {
    x.f()
}

fn main() {
    let a = my_function(13_u32);
    my_function(String::from("abc"));
}

第二个是特征对象方式:

trait MyTrait {
    fn f(&self) -> Box<dyn MyTrait>;
}

impl MyTrait for u32 {
    fn f(&self) -> Box<dyn MyTrait> { Box::new(42) }
}

impl MyTrait for String {
    fn f(&self) -> Box<dyn MyTrait> { Box::new(self.clone()) }
}

fn my_function(x: Box<dyn MyTrait>) -> Box<dyn MyTrait> {
    x.f()
}

fn main() {
    my_function(Box::new(13_u32));
    my_function(Box::new(String::from("abc")));
}

关联类型

关联类型是在特征定义的语句块中,声明一个自定义类型,这样就可以在特征的方法签名中使用该类型:

pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}
impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        // --snip--
    }
}

fn main() {
    let c = Counter{..}
    c.next()
}

结论:由于使用了泛型,导致函数头部也必须增加泛型的声明,而使用关联类型,将得到可读性好得多的代码

struct Container(i32, i32);

// 使用关联类型实现重新实现以下特征

trait Contains {
    type A;
    type B;
    fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
    fn first(&self) -> i32;
    fn last(&self) -> i32;
}

impl Contains for Container {
    type A = i32;
    type B = i32;
    fn contains(&self, number_1: &Self::A, number_2: &Self::B) -> bool {
        (&self.0 == number_1) && (&self.1 == number_2)
    }
    // Grab the first number.
    fn first(&self) -> i32 { self.0 }

    // Grab the last number.
    fn last(&self) -> i32 { self.1 }
}

fn difference<C: Contains>(container: &C) -> i32 {
    container.last() - container.first()
}

fn main() {
    let number_1 = 3;
    let number_2 = 10;

    let container = Container(number_1, number_2);

    println!("Does container contain {} and {}: {}",
        &number_1, &number_2,
        container.contains(&number_1, &number_2));
    println!("First number: {}", container.first());
    println!("Last number: {}", container.last());

    println!("The difference is: {}", difference(&container));
}

默认泛型类型参数

use std::ops::Sub;

#[derive(Debug, PartialEq)]
struct Point<T> {
    x: T,
    y: T,
}

// 1
impl<T: Sub<Output = T>> Sub<Point<T>> for Point<T> {
    type Output = Self;

    fn sub(self, other: Self) -> Self::Output {
        Point {
            x: self.x - other.x,
            y: self.y - other.y,
        }
    }
}
// 2
impl<T: Sub<Output = T>> Sub<Self> for Point<T> {
    type Output = Self;

    fn sub(self, other: Self) -> Self::Output {
        Point {
            x: self.x - other.x,
            y: self.y - other.y,
        }
    }
}
// 3
impl<T: Sub<Output = T>> Sub for Point<T> {
    type Output = Self;

    fn sub(self, other: Self) -> Self::Output {
        Point {
            x: self.x - other.x,
            y: self.y - other.y,
        }
    }
}

fn main() {
    assert_eq!(Point { x: 2, y: 3 } - Point { x: 1, y: 0 },
        Point { x: 1, y: 3 });

    println!("Success!")
}

完全限定语法

调用同名的方法

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*");
    }
}

fn main() {
    let person = Human;
    Pilot::fly(&person); // 调用Pilot特征上的方法
    Wizard::fly(&person); // 调用Wizard特征上的方法
    person.fly(); // 调用Human类型自身的方法
}
trait UsernameWidget {
    fn get(&self) -> String;
}

trait AgeWidget {
    fn get(&self) -> u8;
}

struct Form {
    username: String,
    age: u8,
}

impl UsernameWidget for Form {
    fn get(&self) -> String {
        self.username.clone()
    }
}

impl AgeWidget for Form {
    fn get(&self) -> u8 {
        self.age
    }
}

fn main() {
    let form = Form{
        username: "rustacean".to_owned(),
        age: 28,
    };

    // 如果你反注释下面一行代码,将看到一个错误: Fully Qualified Syntax
    // 毕竟,这里有好几个同名的 `get` 方法
    //
    // println!("{}", form.get());

    let username = UsernameWidget::get(&form);
    assert_eq!("rustacean".to_owned(), username);
    let age = AgeWidget::get(&form); // 你还可以使用以下语法 `<Form as AgeWidget>::get`
    assert_eq!(28, age);

    println!("Success!")
}

关联函数同名

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());
// }

fn main() {
    println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}

完全限定语法定义为:

<Type as Trait>::function(receiver_if_method, next_arg, ...);

特征定义中的特征约束

Supertraits

有些时候我们希望在特征上实现类似继承的特性,例如让一个特征 A 使用另一个特征 B 的功能。这种情况下,一个类型要实现特征 A 首先要实现特征 B, 特征 B 就被称为 supertrait

use std::fmt::Display;

trait OutlinePrint: 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));
    }
}

use std::fmt;

struct Point {
    x: i32,
    y: i32,
}

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

impl OutlinePrint for Point {}
use std::fmt::Debug;

trait Person: Debug {
    fn name(&self) -> String;
}

// Person 是 Student 的 supertrait .
// 实现 Student 需要同时实现 Person.
trait Student: Person {
    fn university(&self) -> String;
}

trait Programmer {
    fn fav_language(&self) -> String;
}

// CompSciStudent (computer science student) 是 Programmer
// 和 Student 的 subtrait. 实现 CompSciStudent 需要先实现这两个 supertraits.
trait CompSciStudent: Programmer + Student {
    fn git_username(&self) -> String;
}

fn comp_sci_student_greeting(student: &dyn CompSciStudent) -> String {
    format!(
        "My name is {} and I attend {}. My favorite language is {}. My Git username is {}",
        student.name(),
        student.university(),
        student.fav_language(),
        student.git_username()
    )
}

#[derive(Debug)]
struct CSStudent {
    name: String,
    university: String,
    fav_language: String,
    git_username: String
}

impl Person for CSStudent {
    fn name(&self) -> String {
        self.name.clone()
    }
}

impl Student for CSStudent {
    fn university(&self) -> String {
        self.university.clone()
    }
}

impl Programmer for CSStudent {
    fn fav_language(&self) -> String {
        self.fav_language.clone()
    }
}

// 为 CSStudent 实现所需的特征
impl CompSciStudent for CSStudent {
    fn git_username(&self) -> String {
        self.git_username.clone()
    }
}

fn main() {
    let student = CSStudent {
        name: "Sunfei".to_string(),
        university: "XXX".to_string(),
        fav_language: "Rust".to_string(),
        git_username: "sunface".to_string()
    };

    // 填空
    println!("{}", comp_sci_student_greeting(&student));
    println!("{:?}", student);
}

在外部类型上实现外部特征(newtype)

这里提供一个办法来绕过孤儿规则(就是特征或者类型必需至少有一个是本地的),那就是使用newtype 模式,简而言之:就是为一个元组结构体创建新类型。该元组结构体封装有一个字段,该字段就是希望实现特征的具体类型。

👇 想给 Vec<String> 加上 Display 特征,但这是个孤儿规则,因为两个定义都不在本地,可以使用newtype技巧。

use std::fmt;

struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}

fn main() {
    let w = Wrapper(vec![String::from("hello"), String::from("world")]);
    println!("w = {}", w);
}
use std::fmt;

// 定义一个 newtype `Pretty`
struct Pretty(String);


impl fmt::Display for Pretty {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "\"{}\"", self.0.clone() + ", world")
    }
}

fn main() {
    let w = Pretty("hello".to_string());
    println!("w = {}", w);
}