【Rust基础】Rust学习笔记 - 第7天

本文作为对Rust的基础学习,建议每一位对Rust感兴趣并且想要接触Rust的同学,将本文中所有例子都自己运行一遍,不要求你手动重写,至少将例子复制下来运行查看效果。

本文基于:

rust: 1.67.0 (fc594f156 2023-01-24)

cargo: 1.67.0 (8ecd4f20a 2023-01-10)

参考文档:Rust 程序设计语言 简体中文版

  • Rust中的泛型

    泛型的概念几乎在每个高级语言中都有,为代码增加泛型的目的其实就是为了更高效的处理重复逻辑;

    初次接触泛型的人可能会对程序中出现的大类<T><P><M>等,头大,或者看不懂,不理解;

    可当一旦熟练使用泛型之后,说是 如鱼得水 也不为过。

    文档中的赘述有点多,这里尽量使用通俗的话来讲解;首先,看下面的例子:

    fn main() {
        let arr = [1, 2, 3, 4, 5];
        let v = vec![1.1, 2.0, 3.5];
    
        print_list(&arr[..]);
        print_list(&v[..]);
    }
    
    //循环输出一个列表
    fn print_list<T>(list: &[T]) {
        for it in list {
            print!("{}、 ", it);
        }
        println!();
    }
    
    

    上述示例,定义了一个名为:print_list 的函数,函数功能则是遍历输出某个列表切片,并且在main中对它进行调用。

    按照逻辑,它应该输出结果:

    123451.123.5

    但是,本文所依赖的运行环境会出现错误提示(旧环境无法考究),

    error[E0277]: `T` doesn't implement `std::fmt::Display`
      --> src\main.rs:11:24
       |
    11 |         print!("{}、 ", it);
       |                         ^^ `T` cannot be formatted with the default formatter
       |
       = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
       = note: this error originates in the macro `$crate::format_args` which comes from the expansion of the macro `print` (in Nightly builds, run with -Z macro-backtrace for more info)
    help: consider restricting type parameter `T`
       |
    9  | fn print_list<T: std::fmt::Display>(list: &[T]) {
       |   
    

    依照提示,为其加上: std::fmt::Display即可:

    fn main() {
        let arr = [1, 2, 3, 4, 5];
        let v = vec![1.1, 2.0, 3.5];
    
        print_list(&arr[..]);
        print_list(&v[..]);
    }
    
    fn print_list<T: std::fmt::Display>(list: &[T]) {
        for it in list {
            print!("{}、 ", it);
        }
        println!();
    }
    

    这样就可以成功运行看到前文所说的结果了。

    说回来泛型,可以看到print_list<T>增加了一个<T>,并且参数list:&[T]中也将T使用了,那么这个<T>可以将它看做类型通配符,即<T>可以是任意类型,正如main函数中的两次调用,一个是i32数组的切片&[i32],一个是 f64 vector的切片&[f64],它们类型不同,却又都调用了print_list函数。

    再说得简单一点:

    print_list<T>可以是print_list<i32>也可以是print_list<f64>或者说其他的print_list<char>等等。

    这既是泛型最通俗的解释,至于何时使用泛型,则需要根据程序中相应的情况来看,比如Rust文档中的 Point 结构体,通俗逻辑下x,y坐标可以是整数也可以是小数,如果不通过泛型,那至少应该写两个Point结构:

    struct PointF {
        x: f64,
        y: f64,
    }
    
    struct PointI {
        x: i32,
        y: i32,
    }
    

    而泛型的出现,让它更加简单了,同时也少写了一遍除了数据类型不同,其他都相同的代码:

    struct Point<T> {
        x: T,
        y: T,
    }
    
  • Rust中的Trait(接口)

    编程中,接口的概念其实就是定义了一套行为规范,用通俗的话来讲,就是给了你一套图纸,你可以通过这套图纸来设计你的程序;Rust中,通过Trait定义一套行为规范(设计图纸),然后你就可以将图纸交给任意一个开发者,让他来遵循图纸来设计程序。

    例如:对于汽车来说,汽车最基本的功能就是能,那么,我们可以给一个顶层的行为run,每一台汽车都应该具有run的功能:

    pub trait Car {
        fn run(&self);
    }
    

    上述代码中,Car是由trait修饰的,它提供了一个方法run,却没有方法体(可以将它看做C语言中的函数声明),这就是汽车的最简单图纸,一旦按照这个图纸设计汽车,run则是必不可少的功能。

    现在我们来造一辆SUV:

    struct Suv {
        name: String,
    }
    
    impl Car for Suv {
        fn run(&self) {
            println!("{}, 行驶中..", self.name);
        }
    }
    

    上述代码实现了 trait Car 的基本功能,可以看到Rust中对于接口(trait)的实现与其他编程语言略有不同:impl 接口(trait) for 结构体(struct){ ... }

    不过它有一点却与其他编程语言类似,也就是 方法 的抽象性:

    • trait 中的某些方法没有方法体时,实现该trait的结构则必须提供方法体。
    pub trait Car {
        fn run(&self){
            println!("行驶中...");
        }
    }
    
    struct Suv {
        name: String,
    }
    
    impl Car for Suv {
        //如果基本功能满足, 则可以省略
        /* fn run(&self) {
            println!("{}, 行驶中..", self.name);
        } */
    }
    

    接口多态

    一旦涉及到接口,必定会有一个多态性,通俗来讲,大众被称为汽车,宝马也被称为汽车。

    fn main() {
        let suv = Suv {
            name: "大众".to_string(),
        };
    
        let bwm = Bwm {
            name: "宝马".to_string(),
        };
    
        who_run(&suv);
        who_run(&bwm);
    }
    
    //到底是谁在运行
    fn who_run(car: &impl Car) {
        car.run();
    }
    
    
    //
    pub trait Car {
        fn run(&self);
    }
    
    struct Suv {
        name: String,
    }
    
    impl Car for Suv {
        fn run(&self) {
            println!("{}, 行驶中..", self.name);
        }
    }
    
    struct Bwm {
        name: String,
    }
    
    impl Car for Bwm {
        fn run(&self) {
            println!("{}, 行驶中..", self.name);
        }
    }
    
    

    输出:

    大众, 行驶中..
    宝马, 行驶中..
    

    同样的,可以用泛型直接指代(我个人更趋向于这种写法)文档称它为trait bound

    //到底是谁在运行
    fn who_run<T: Car>(car: &T) {
        car.run()
    }
    

    trait多实现

    fn main() {
        let suv = Suv {
            name: "大众".to_string(),
        };
    
        let bwm = Bwm {
            name: "宝马".to_string(),
        };
    
        who_run(&suv);
        who_run(&bwm);
    }
    
    //到底是谁在运行
    fn who_run<T: Car + Color>(car: &T) {
        car.run();
        car.color();
    }
    
    
    //
    pub trait Car {
        fn run(&self);
    }
    
    pub trait Color {
        fn color(&self);
    }
    
    struct Suv {
        name: String,
    }
    
    impl Car for Suv {
        fn run(&self) {
            println!("{}, 行驶中..", self.name);
        }
    }
    
    impl Color for Suv {
        fn color(&self) {
            println!("红色");
        }
    }
    
    struct Bwm {
        name: String,
    }
    
    impl Car for Bwm {
        fn run(&self) {
            println!("{}, 行驶中..", self.name);
        }
    }
    
    impl Color for Bwm {
        fn color(&self) {
            println!("紫色");
        }
    }
    

    上述代码中又给定了一个Trait Color,并分别让SuvBwm实现;

    然后在fn who_run<T: Car + Color>(car: &T) 通过+的方式,对多实现结构进行操作。

    对于Rust中trait的介绍暂时到这,文档中提到的 闭包 将在 闭包 一节介绍。

时间:2023年2月15日 第7天


版权声明:本文为AoXue2017原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。