RUST第十节(上) - 泛型和特征

10 泛型和特征(trait)

10.1 泛型

10.1.1 什么是泛型

在讲泛型之前,我们先来抽象一个公用的求最大值的函数,例如:

rust 复制代码
fn max(arr: &[i32]) -> i32 {
    let mut largest: i32 = arr[0];
    for item in arr {
        if *item > largest {
            largest = *item;
        }
    }
    largest
}
let largest = max(&[100, 2, 3, 4]);
println!("{}", largest);

通过上面的对于方法的一个抽象封装,我们可以同样来理解泛型。 泛型其实就是对于类型的抽象。接下来,我们先来讲讲泛型的命名并对上面的函数再次进行泛型的封装。

在Rust中,我们一般对泛型采用极为简洁的命名,一个大写的字母 ,比如:T,U等等,当然如果你的命名比较长,那就使用大驼峰命名

那现在我们就来实现对上面函数的类型使用泛型,例如:

rust 复制代码
fn max<T>(arr: &[T]) -> T {
    let mut largest: T = arr[0];
    for item in arr {
        if *item > largest { // 比较会报错 consider restricting type parameter `T`: `:std::cmp::PartialOrd`
            largest = *item;
        }
    }
    largest
}

注意 使用泛型之后,我们上面的比较是会报错的,这是为什么呢? 因为我们传入的泛型可以理解为,可以传入任何类型 ,但是不是所有类型都具有比较这个行为的,所以编译的时候,Rust就会给我们提示出来。

这个问题,我们会在后面的trait 特征章节来解决。

10.1.2 函数 && 泛型

在上面的列子中我们也看到了,怎么在一个函数中定义泛型。那就是在函数名之后使用<>尖括号将我们的泛型传入,这个时候我们就可以在函数中使用它了。例如:

rust 复制代码
fn test<T, U>() {} //可以传入多个泛型

10.1.3 结构体 && 泛型

在结构体里面使用泛型,我们需要在结构体名后面的使用<>尖括号将泛型传入,例如:

rust 复制代码
struct Point<T> {
    x: T,
    y: T,
}

10.1.4 枚举 && 泛型

对于枚举类型,我们可以在枚举名后面使用<>尖括号将泛型传入,例如:

rust 复制代码
enum Color<T, U, K> {
    Red(T),
    Green(U),
    Blue(K),
}

10.1.5 方法 && 泛型

如果我们需要为一个方法添加泛型,我们需要在impl后使用<>尖括号将泛型传入,例如

rust 复制代码
#[derive(Debug)]
struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn new(x: T, y: T) -> Point<T> {
        Point { x, y }
    }
}

let p: Point<f64> = Point::new(1.0, 2.0);
println!("{:#?}", p);

10.2 特征(trait)

10.2.1 定义trait

对于特征,可以简单理解为:对于不同或者相同类型的方法的抽象。 我们使用trait来定义特征,例如:

rust 复制代码
trait Food {
    fn eat(&self) -> ();
    fn add() -> ();
}

struct Home;

impl Food for Home {
    fn eat(&self) -> () {
        println!("eat vegetable")
    }
    fn add() -> () {
        println!("add food")
    }
}

struct Company;
impl Food for Company {
    fn eat(&self) -> () {
        println!("eat buff")
    }
    fn add() -> () {
        println!("add drinkings")
    }
}

let home = Home {};
home.eat();
Home::add();

let company = Company {};
company.eat();
Company::add();

我们在上面定义了一个特征Food,然后我们使用impl for的语法为类型HomeCompany添加了这个特征,并且自定义了方法的内容

10.2.2 特征默认行为

我们其实也可以给给特征定义默认的方法,例如:

rust 复制代码
trait Food {
    // 定义方法
    fn eat(&self) {
        println!("eat anything")
    }
}

10.2.3 特征约束

我们首先简单说一下什么是特征约束 ,就是我们可以在泛型后面使用:加上我们定义的特征或者第三方的特征,当只有我们传入了实现了我们传入特征的类型,才能通过编译。

我们定义的特征可以作为一个参数传入到函数或者方法中,例如:

rust 复制代码
trait Food {
    // 定义方法
    fn eat(&self) -> () {
        println!("hh")
    }
}

fn home_eat(item: impl Food) {
    item.eat();
}

struct Home;
// 当我们不为Home声明Food特征时,就会发生编译错误,因为我们对item做了行为约束
impl Food for Home { 
    fn eat(&self) -> () {
        println!("eat")
    }
}
home_eat(Home)

如果我们对多个参数都要做这种约束,那么上面的写法会相当冗杂,我们可以结合泛型来做约束,我们将上面的home_eat修改一下,加上泛型T并且使用特征Food去约束它,例如:

rust 复制代码
fn home_eat<T: Food>(item: T) {
    item.eat();
}

那如果我们需要对一个泛型实现多个特征的约束呢?我们可以直接使用+将多个特征加起来,例如:

rust 复制代码
trait Food {
    fn eat(&self) -> ();
}
trait Water {
    fn drink(&self) -> ();
}

fn home_eat<T: Food + Water>(item: T) {
    // 这个时候,我们传入的Item必须实现Food的eat方法和Water的drink方法
    item.eat();
    item.drink();
}

struct Home;
impl Food for Home {
    fn eat(&self) -> () {
        println!("eat")
    }
}
impl Water for Home {
    fn drink(&self) -> () {
        println!("drink")
    }
}
home_eat(Home);

上面的写法还可以使用where来优化,例如:

rust 复制代码
fn home_eat<T>(item: T)
where
    T: Food + Water,
{
    // 这个时候,我们传入的Item必须实现Food的eat方法和Water的drink方法
    item.eat();
    item.drink();
}

我们还可以用特征来约束一个返回值,继续上面的修改,当我想返回一个带有Food特征的返回值时,我们可以这样:

rust 复制代码
fn home_eat<T>(item: T) -> impl Food // 只需要在末尾加上impl + 特征
where
    T: Food + Water,
{
    // 这个时候,我们传入的Item必须实现Food的eat方法和Water的drink方法
    item.eat();
    item.drink();
    item // item是实现了Food特征的,所以返回是不会报错的
}

上面我们都是对一个函数做约束,拿要是需要对我们为结构体声明的方法进行约束,那应该怎么做呢?只需要在impl后面的泛型加上特征约束即可,例如:

rust 复制代码
struct Rectangle<T> {
    width: T,
    height: T,
}

impl<T: Copy> Rectangle<T> {
    fn new(x: T, y: T) -> Rectangle<T> {
        Rectangle {
            width: x,
            height: y,
        }
    }
}

// 正确,因为整数实现了Copy特征,符合条件
let rect = Rectangle::new(1, 1);

// 报错,因为动态数组并没有实现Copy特征
let rect1 = Rectangle::new(vec![1], vec![1]);

最后我们来看看我们最开始定义的largest函数,我们来使用泛型约束它,让它能够正确的进行比较,例如:

rust 复制代码
fn max<T>(arr: &[T]) -> T
where
   T: Copy + PartialOrd,
{
   let mut largest: T = arr[0];
   for item in arr {
       if *item > largest {
           largest = *item;
       }
   }
   largest
}

我们给它加上了可以Copy可复制和PartialOrd可以比较的特征,我们在传入参数的时候就会对参数进行这两个特征的限制,就不能随意传值了。

相关推荐
小钻风336621 分钟前
深入浅出掌握 Axios(持续更新)
前端·javascript·axios
萌萌哒草头将军29 分钟前
🚀🚀🚀尤雨溪推荐的这个库你一定要知道!轻量⚡️,优雅!
前端·vue.js·react.js
三门42 分钟前
docker安装mysql8.0.20过程
前端
BillKu1 小时前
Vue3 + Vite 中使用 Lodash-es 的防抖 debounce 详解
前端·javascript·vue.js
一只小风华~1 小时前
HTML前端开发:JavaScript的条分支语句if,Switch
前端·javascript·html5
橙子家1 小时前
Select 组件实现【全选】(基于 Element)
前端
超级土豆粉1 小时前
HTML 语义化
前端·html
bingbingyihao2 小时前
UI框架-通知组件
前端·javascript·vue
wordbaby2 小时前
React Router 预渲染的工作原理和价值(Pre-rendering)
前端·react.js
依旧天真无邪2 小时前
Chrome 优质插件计划
前端·chrome