当前位置: 首页>后端>正文

关于rust中trait(一)

概述

相对其他语言(java/C#)提供了接口来满足对不同类型的值进行操作的代码(甚至包括那些尚未实现的类型),并能够结合泛型来实现语言的“多态化”;同样Rust为了达到对“多态”的支持提供了trait(特型)和generic(泛型)。
而trait(特型)算是rust对接口或抽象基类的实现。
举个类型: 标准库中关于Write定义 std::io::Write

trait Write {
    fn write(&mut self, buf: &[u8]) -> Result<usize>;
    fn flush(&mut self) -> Result<()>;
       fn write_all(&mut self, buf: &[u8]) -> Result<()> {
        // 省略部分代码
       }
       // 省略代码
}

同样在标准类型库中File、TcpStream和Vec<u8>等实现了std::io::Write

use std::io::Write; // 需要引入对应的trait

fn say_hello(out: &mut Write) -> std::io::Result<()> {
    out,write_all(b"welcome to rust world!!!")?;    // 使用Vec<u8> 
    out.flush();
}

///  测试用例
use std::fs::File;
fn main() {
    let mut file = std::fs::File::create("hello_world.txt").unwrap();
    say_hello(&mut file);

    let mut bytes = vec![];
    say_hello(&mut bytes);
    println!("bytes={:?}", bytes);
}

同样使用trait(特型)添加给类型不用额外内存

使用trait(特型)

在rust中trait(特型)是一种任何类型都可以支持或不支持;trait可以认为某类型能够做什么的一种能力;(当使用trait特型方法时需要确保其本身必须存在当前作用域中,否则trait特型所有的方法都是不可用的;若是trait特型是标准前置模块的一部分,这样rust就会默认自动将其引入)。
1、trait object(特型对象/特型目标)

use std::io::Write;
fn main()  {
    let mut buf: Vec<u8> = vec![];

    // let writer: Write = buf; // 不能通过编译,编译时不知道write真实大小
    let writer: &mut Write = &mut buf; 
}

上面的例子中line-error-1这行是不能通过编译的,在rust中变量大小在编译期间必须可知的,但是实现Write的类型大小是任意的,这一点不同于其他语言(java/C#),因为这些语言默认定义的变量是一个引用,并执行任何实现了interface或abstract的对象;但是在rust的引用必须是显式的。并且在rust将一个trait特型类型的引用称为trait object(特型对象/特型目标)。
不过需要知道的是:trait object(特型对象/特型目标)在编译期间通常是不知道引用对象/目标的类型,那就需要trait object(特型对象/特型目标)包含一些关于trait object(特型对象/特型目标)的额外信息,并且这个信息仅限rust内部自身使用,确保在调用trait特型方法时知道类型信息,这样才能动态调用正确的方法。

2、trait object(特型对象/特型目标)的内存分布
在内存中, trait object(特型对象/特型目标)是一个胖指针,包含指向值的指针和指向表示该值类型的表的指针; 每个trait object(特型对象/特型目标)都会占用两个机器字;

let mut buf: Vec<u8> = vec![];

let writer: &mut Write = &mut buf;  

内存布局如下:

data:数据指针内容 &mut buf as &mut Write 值类型的表指针
buf data:数据指针 destructor
buffer:数据 vptr :值类型的表指针 size
capacity: 容量 alignment
length: 大小 .wirte()
.flush()
.write_all()

在rust中会在编译时生成虚拟表并且只生成一次,并有同类型的所有对象共享(impl trait_name for traint_imple_name);这样在调用一个trait object(特型对象/特型目标)的方法时,会自动使用虚拟表,已确定调用了哪个实现。

3、traint特型的定义和实现
在前面的例子中定义trait特型其实是很简单的:

关键字trait Trait_Name {

// trait体 定义不同的方法
// 方法

}

样例:

trait Action {
    fn draw(&self, canvas: &mut Canvas);
    fn hit_test(&self, x:i32, y:i32) -> bool;
}

同样,实现trait也是相对比较简单的:

关键字 impl Trait_Name for Type_Name {
// 实现的方法

}

样例:

impl Action for Broom {
    fn draw(&self, canvas: &mut Canvas) {
        for y in self.y - self.height -1 .. self.y {
            canvas.write_at(self.x, y, '|');
        }
        canvas.write_at(self.x, self.y, 'M');
    }

    fn hit_test(&self, x: i32, y: i32)  -> bool {
        self.x == x 
        && self.y - self.height - 1 <= y 
        && y <= self.y
    }
}

而非trait特型的方法,可以使用impl Type_Name来定义:

impl Type_Name {
    fn method_name(&self, ...<参数>...);
    fn method_name(...<参数>...);
    ...
}

不过在实现trait时,若是其中某个或某些方法相对通用,可以在trait特型中提供当前方法的默认实现。 这样通过impl Trait_Name for Type_Name 语法来实现trait时,可以使用这个默认方法。

在Rust允许在任意类型上实现任意trait特型,这样就可以给任意类型扩张功能:

样例: 扩展现有char类型的功能

trait IsEmoji {
    fn is_emlji(&self) -> bool;
}

impl IsEmoji for char {
    fn is_emoji(&self) -> bool {
        ...
    }
}

同样,可以一次性全部实现一个扩展trait特型: 使用泛型impl语法让一个类型“家族”都具有某个扩展trait特型(关于泛型这块会有单独的介绍)。

样例:对每个实现Write的类型,在实现trait特型WriteHtml

use std::io::{self, Write};

trait WriteHtml {
    fn write_html(&mut self, html: &HtmlDocument) -> io::Result;
}

impl<W: Write> WriteHtml for W {
    fn write_html(&mut self, html: &HtmlDocument) -> io::Result<()> {
        ......
    }
}

在rust中trait特型也能够实现其他语言的“继承”, 通过impl来实现trait时需要实现当前trait特型及其父trait特型。

样例:子trait特型

trait Trait_Name: Parent_Trait_Name {
    ...
}

引用

trait特型

rust之trait-知乎


https://www.xamrdz.com/backend/3w51936922.html

相关文章: