rust语言trait特性综述,老于聊技术

Trait为rust强大的抽象表达能力注入了灵魂,融入rust各个角落, rust集众家之长,去其糟粕,若是没有了trait, 我认为rust的优雅将黯然失色!rust中各种约束和限定主要由trait来表达,系统优雅严密,好比胶水,将泛型、并发、异步、内存管理等等诸多语言特性优雅地融合到一起,设计可谓精妙。

 

  • 定义一个Trait
pub trait Summary {
    fn summarize(&self) -> String;
    //可以定义许多接口方法,类似于java的接口和golang的interface.
}

 

  • 实现Trait
use std::fmt;
//下面的impl ... for Point相当于derive语句。
//#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

impl fmt::Debug for Point {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Point")
         .field("x", &self.x)
         .field("y", &self.y)
         .finish()
    }
}

let origin = Point { x: 0, y: 0 };

assert_eq!(format!("The origin is: {:?}", origin), "The origin is: Point { x: 0, y: 0 }");

 

  • Box<dyn aTrait>

rust编译器要求函数的参数和返回值的大小必须静态已知的,即a statically-known size, 换个说法就是大小编译期间可知,但是实际编程开发时,有些类型的大小只有在运行时才知道。

struct Sheep {}
struct Cow {}

trait Animal {
    fn noise(&self) -> &'static str;
}

impl Animal for Sheep {
    fn noise(&self) -> &'static str {
        "for Sheep!"
    }
}

impl Animal for Cow {
    fn noise(&self) -> &'static str {
        "for Cow!"
    }
}

// Returns some struct that implements Animal, but we don't know which one at compile time.
//很明显,下面函数的返回值大小只有在运行时才能知道,编译器无从知晓。
//所以采用Box<dyn aTrait>来做到大小编译期可知,好似C/C++的void指针和golang的interface{}空接口,
//只不过进一步限定为实现了Animal Trait
//的具体类型,如:上面的Sheep 和 Cow struct。
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());
}

 

  • 运算符重载

rust不允许C++方式的函数重载,因为C++的函数重载特性确实非常强大灵活, 不过却也是雷区沼泽,项目持续膨胀长期维护时很容易触雷,所以现实的工程实践证明,其实C++现有的函数重载, 后期演化出泛型,当其泛型成熟时,其实函数重载的重要性就削弱了,为了避雷,少用为妙,但是运算符的重载还是十分必要的, 所以rust通过Trait来实现部分运算符的重载, 典型的严肃活泼,严谨也不失灵活。

use std::ops;

struct Foo;
struct Bar;

#[derive(Debug)]
struct FooBar;

#[derive(Debug)]
struct BarFoo;

//std::ops::Add对应运算符'+'的功能。
// Here, we make `Add<Bar>` - the trait for addition with a RHS of type `Bar`.
//struct Bar对象作为加号'+'右侧的元素, 即形如:Foo + Bar
impl ops::Add<Bar> for Foo {
    type Output = FooBar;

    fn add(self, _rhs: Bar) -> FooBar {
        println!("> Foo.add(Bar) was called");

        FooBar //提醒,在rust中,表达式末尾没带分号,等同于return FooBar;
    }
}

// By reversing the types, we end up implementing non-commutative addition.
//加法支持交换率,所以需要同时实现Bar + Foo , 这次Foo作为加号右侧的元素。
impl ops::Add<Foo> for Bar {
    type Output = BarFoo;

    fn add(self, _rhs: Foo) -> BarFoo {
        println!("> Bar.add(Foo) was called");

        BarFoo
    }
}

fn main() {
    println!("Foo + Bar = {:?}", Foo + Bar); 
    //编译器见到加号‘+’会自动寻找匹配的Add Trait impl block。
    println!("Bar + Foo = {:?}", Bar + Foo);
}

 

  • 泛型

泛型不是个新概念了,各个主流变成语言都有支持, 具体形式也许有些差别, 但本质核心就是将类型参数化,编译器编译期间可以推导出具体类型, rust语言集众家之长,自然也支持泛型, 但是他的特色在于和Trait融合到一起, 迸发出更加强大灵活严谨高效的抽象表达能力,可以充当定语, 有效限制和明确各种元素的声明和定义。

//Trait泛型化基础例子。
//没有实现Copy Trait的自定义类型,通常默认为Move语义。
struct Empty;
struct Null;

//将这个Tait通过类型参数T泛型化。
trait DoubleDrop<T> {
    //这个方法实现为空,形参借用形式默认为Move语义, 意味着接管传入的实参所有权,
    //成为owner后,下面方法执行完毕后,实参自动被析构,
    //接管self和T的所有权,方法执行完毕,然后自动析构。
    fn double_drop(self, _: T);
}

//U就是double_drop方法的调用者。
//T就是double_drop方法的实参。
impl<T, U> DoubleDrop<T> for U {
    // This method takes ownership of both passed arguments,
    // deallocating both.
    //接管self和T的所有权,方法执行完毕后自动析构。
    fn double_drop(self, _: T) {}
}

fn main() {
    let empty = Empty;
    let null  = Null;

    // Deallocate `empty` and `null`.
    //这条语句执行完毕后,empty和null都会被自动析构。
    empty.double_drop(null);

    //下面两条语句必须注释掉,因为empty和null在上面已经被自动析构,
    //此处属于非法访问已析构的对象, 编译器必然报错,无法编译通过。
    //empty;
    //null;
}

 

  • Trait充当限制约束条件说明
//限定函数形参T必须为实现了Display Trait的类型。
fn printer<T: Display>(t: T) {
    println!("{}", t);
}

 

  • 空Trait定义

空Trait被rust编译器广泛用于给具体类型打标签,验明正身之用,归类之用; 比如:Send、Sync、Copy、Sized等等,详情可参考std::marker::* , 比如用Sized Tait标定你的类型属于编译期间大小可知的具体类型,用Copy Trait标定你的类型采用复制语义而非默认的移动语义, 用Send Tait标明你的类型对象可以在多线程间传递所有权使用, 用Sync Tait标明你的类型对象可以被多线程共享访问,是安全的。标准库里好多类型都被rust标定好了, 而你自己的自定义类型也可以自己标定,向编译器声明自己属于哪一类,接受编译器的专项审查, 如果不合格则被编译器视为非法,拒绝编译通过。rust通过各种Trait来实现各种审查和认可,从而实现诸如线程安全,内存安全等核心特性。

struct Cardinal;
struct BlueJay;
struct Turkey;

//trait定义中没有方法。
trait Red {} 
trait Blue {}

//一个方法也没有实现,只是表明了这个具体类型实现了Red或Blue Trait这个事实而已。
impl Red for Cardinal {}
impl Blue for BlueJay {}

//下面的函数定义是合法有效的, 即使Red和Blue Trait为空定义体也没有关系。
//T:Read用于向编译器声明自己只允许实现了Red Trait的实参传入,其他拒绝。
fn red<T: Red>(_: &T)   -> &'static str { "red" }
fn blue<T: Blue>(_: &T) -> &'static str { "blue" }

fn main() {
    let cardinal = Cardinal;
    let blue_jay = BlueJay;
    let _turkey   = Turkey;

    //red函数只允许实现了Red Trait的实参传入。
    println!("A cardinal is {}", red(&cardinal));
    //blue函数只允许实现了Blue Trait的实参传入。
    println!("A blue jay is {}", blue(&blue_jay));
    //下面注释掉的函数调用语句为非法,因为red函数向编译器生命他只接受实现了Red Trait的实参,
    //其他视为非法, 所以编译器报错。
    //println!("A turkey is {}", red(&_turkey));
    
}

 

  • Multiple bounds 使用‘+’号 & where

trait在此处充当多条件的约束限制表述,诸如此类的用法在rust中非常普遍。

//伪码举例
impl <A: TraitB + TraitC, D: TraitE + TraitF> MyTrait<A, D> for YourType {
    
}

// Expressing bounds with a `where` clause
impl <A, D> MyTrait<A, D> for YourType where
    A: TraitB + TraitC,
    D: TraitE + TraitF {
        
  }

 

  • Supertraits

rust不允许类型间继承, 但是支持组合, 然而trait之间以好像继承的形式实则是用组合的本质来组织trait, 子 trait相当于超集合, 而父trait相当于子集合,也就是说如果你要impl 一个子 trait, 那么也必须同时Impl其父trait, 因为他们是组合不是继承哟,切记!敏捷软件开发认为: 组合优于继承,这也是工程界的共识经验。

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

//Person现在是Student的父集, 而Student称为Person的子集。
//所以rust要求impl子集的同时也必须impl父集。
trait Student: Person {
    fn university(&self) -> String;
}

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

trait CompSciStudent: Programmer + Student {
    fn git_username(&self) -> String;
}


struct Foo {};
impl CompSciStudent for Foo {
     fn git_username(&self) -> String {
         String::from("CompSciStudent ")
     }
}

//注意在impl CompSciStudent trait的时候,必须同时impl trait Programmer and Student and Person.
impl Programmer for Foo {
      fn fav_language(&self) -> String {
           String::from("Programmer ")
      }
}
impl Student for Foo {
      fn university(&self) -> String {
          String::from("Student")
      }
}

impl Person for Foo {
     fn name(&self) -> String {
         String::from("Person ") 
     }
}

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

 

  • 不同Trait拥有同名方法怎么办

对了我们需要给方法指定 Fully Qualified Syntax(完全限定语法),避免模糊。

trait UsernameWidget {
    // Get the selected username out of this widget
    fn get(&self) -> String;
}

trait AgeWidget {
    // Get the selected age out of this widget
    fn get(&self) -> u8;
}

// A form with both a UsernameWidget and an AgeWidget
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,
    };

    //和明显form impl两个trait ,而且方法同名,
    //所以下面语句编译报错,编译器不知道你具体想调用哪个get方法。
    // println!("{}", form.get());

    //下面语句就是“完全限定语法”帮助编译器找到正确的get方法。
    let username = <Form as UsernameWidget>::get(&form);
    assert_eq!("rustacean".to_owned(), username);
    let age = <Form as AgeWidget>::get(&form);
    assert_eq!(28, age);
}

最后强调一下,rust的默认语义是move, 所以如果你的自定义类型想要采用复制语义的话, 请为其同时impl Copy and Clone 这两个trait。说个例外,rust中部分基础类型,如数值、char、bool等默认为复制语义,都默认实现了Copy and Clone trait, 这是rust为了编程方便开了个例外,而且这些基础类型非常小,采用复制方式效率更高。

 

  • 采用impl a Trait简化函数返回值声明

impl a Trait这种语法主要用于简化函数形参和返回值声明时太过啰嗦的问题。

use std::iter;
use std::vec::IntoIter;

// This function combines two `Vec<i32>` and returns an iterator over it.
// Look how complicated its return type is!
//看,这个函数的返回值类型声明够啰嗦的吧!
fn combine_vecs_explicit_return_type(
    v: Vec<i32>,
    u: Vec<i32>,
) -> iter::Cycle<iter::Chain<IntoIter<i32>, IntoIter<i32>>> {
    v.into_iter().chain(u.into_iter()).cycle()
}

// This is the exact same function, but its return type uses `impl Trait`.
// Look how much simpler it is!
//采用impl a trait的语法来大幅度简化返回值类型声明。
//表达更简洁。
fn combine_vecs(
    v: Vec<i32>,
    u: Vec<i32>,
) -> impl Iterator<Item=i32> {
    v.into_iter().chain(u.into_iter()).cycle()
}

fn main() {
    let v1 = vec![1, 2, 3];
    let v2 = vec![4, 5];
    let mut v3 = combine_vecs(v1, v2);
    assert_eq!(Some(1), v3.next());
    assert_eq!(Some(2), v3.next());
    assert_eq!(Some(3), v3.next());
    assert_eq!(Some(4), v3.next());
    assert_eq!(Some(5), v3.next());
    println!("all done");
}

 

  • 采用impl a Trait来简化闭包的声明
// Returns a function that adds `y` to its input
//这个函数返回一个闭包, 闭包的类型采用impl方式简化声明。
fn make_adder_function(y: i32) -> impl Fn(i32) -> i32 {
    let closure = move |x: i32| { x + y };
    closure
}

fn main() {
    let plus_one = make_adder_function(1);
    assert_eq!(plus_one(2), 3);
}

 

注意:

(1)impl Copy trait只是说明某个类型采用复制语义,但是真正的复制操作必须

同时impl Clone trait。

(2)在rust中不允许你手动直接去析构释放某个对象, 一切由编译器自动分析

识别在适当位置插入析构释放代码, 不允许手工干预, 但是可以调用

对某个对象调用 std::mem::drop函数通知请求编译器在未超出此对象作用域

的时候,提前析构释放此对象,记住是请求, 编译器会酌情处理。

(3)如果要为自定义类型定义析构函数, 则必须impl Drop trait.

(4)如果你想要自定义一个迭代器,则必须impl Iterator trait.

 

特别声明:本文中所采用的代码皆摘录于rust官方参考文档中,如:

https://doc.rust-lang.org/stable/rust-by-example/index.html

https://doc.rust-lang.org/book/title-page.html#the-rust-programming-language

https://doc.rust-lang.org/std/index.html

我的rust学习代码和资料:https://github.com/yujinliang/rust_learn

rust是近几十年以来编程领域工程实践的集大成之作,博采众家之长,去其糟粕,独树一帜,为系统级编程语言开出了一条新路!他的潜力只露冰山一角,未来随着各大科技企业巨头和开源社区的持续关注和投入, rust必然会迸发出惊人的能量。


对了,老于以前的网名叫:心尘了, 现在更名为:灵山行者。

老码农爱学习,爱感悟,爱分享。

喜欢老于就请您关注我,点赞评论转发,您的支持对我很重要,可以支持我走下去,老码农想要探索新的活法, 除了键盘,我应该还可以去探索体悟这个多彩的世界!

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值