鍍金池/ 教程/ Java/ trait對(duì)象 (trait object)
標(biāo)準(zhǔn)輸入與輸出
消息傳遞
循環(huán)
注釋
Rust for Mac OS
幾種智能指針
Cell, RefCell
trait對(duì)象 (trait object)
rust web 開(kāi)發(fā)
Unsafe、原始指針
Macro
迭代器
函數(shù)
Borrow, BorrowMut, ToOwned
快速上手
二叉樹(shù)
編輯器
測(cè)試與評(píng)測(cè)
Deref
安裝Rust
哈希表 HashMap
原生類型
17.錯(cuò)誤處理
VS Code 安裝配置
動(dòng)態(tài)數(shù)組Vec
模式匹配
操作符和格式化字符串
Rust for Linux
函數(shù)參數(shù)
Visual Studio
vim/GVim安裝配置
閉包作為參數(shù)和返回值
安全(Safety)
Cow
生命周期( Lifetime )
閉包的實(shí)現(xiàn)
所有權(quán)(Ownership)
Atom
將Rust編譯成庫(kù)
類型、運(yùn)算符和字符串
類型系統(tǒng)中的幾個(gè)常見(jiàn) trait
特性
屬性和編譯器參數(shù)
Spacemacs
集合類型
Rust json處理
Heap & Stack
并行
標(biāo)準(zhǔn)庫(kù)示例
基本程序結(jié)構(gòu)
鏈表
trait 和 trait對(duì)象
前期準(zhǔn)備
代碼風(fēng)格
編譯器參數(shù)
基于語(yǔ)義化版本的項(xiàng)目版本聲明與管理
Rust 版本管理工具: rustup
引用&借用(References&Borrowing)
注釋與文檔
10.1 trait關(guān)鍵字
模式
調(diào)用ffi函數(shù)
unsafe
并發(fā),并行,多線程編程
AsRef 和 AsMut
Rust旅程
Rust for Windows
結(jié)構(gòu)體與枚舉
條件分支
附錄I-術(shù)語(yǔ)表
變量綁定與原生類型
Mutex 與 RwLock
泛型
裸指針
常用數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)
系統(tǒng)命令:調(diào)用grep
Into/From 及其在 String 和 &str 互轉(zhuǎn)上的應(yīng)用
共享內(nèi)存
Sublime
網(wǎng)絡(luò)模塊:W貓的回音
函數(shù)返回值
包和模塊
高階函數(shù)
函數(shù)與方法
match關(guān)鍵字
隊(duì)列
目錄操作:簡(jiǎn)單grep
語(yǔ)句和表達(dá)式
并發(fā)編程
閉包
測(cè)試
閉包的語(yǔ)法
同步
迭代器
String
Send 和 Sync
Rc 和 Arc
屬性
Emacs
優(yōu)先隊(duì)列
Prelude
cargo簡(jiǎn)介
控制流(control flow)
數(shù)組、動(dòng)態(tài)數(shù)組和字符串
FFI
模塊和包系統(tǒng)、Prelude
實(shí)戰(zhàn)篇
Rust 是一門系統(tǒng)級(jí)編程語(yǔ)言,被設(shè)計(jì)為保證內(nèi)存和線程安全,并防止段錯(cuò)誤。作為系統(tǒng)級(jí)編程語(yǔ)言,它的基本理念是 “零開(kāi)銷抽象”。理
運(yùn)算符重載
Any和反射
rust數(shù)據(jù)庫(kù)操作
輸入輸出流
復(fù)合類型
性能測(cè)試

trait對(duì)象 (trait object)

trait對(duì)象在Rust中是指使用指針?lè)庋b了的 trait,比如 &SomeTraitBox<SomeTrait>。

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 do_something(x: &Foo) {
    x.method();
}

fn main() {
    let x = "Hello".to_string();
    do_something(&x);
    let y = 8u8;
    do_something(&y);
}

x: &Foo其中x是一個(gè)trait對(duì)象,這里用指針是因?yàn)?code>x可以是任意實(shí)現(xiàn)Foo的類型實(shí)例,內(nèi)存大小并不確定,但指針的大小是固定的。

trait對(duì)象的實(shí)現(xiàn)

&SomeTrait 類型和普通的指針類型&i32不同。它不僅包括指向真實(shí)對(duì)象的指針,還包括一個(gè)指向虛函數(shù)表的指針。它的內(nèi)部實(shí)現(xiàn)定義在在std::raw模塊中:

pub struct TraitObject {
    pub data: *mut (),
    pub vtable: *mut (),
}

其中data是一個(gè)指向?qū)嶋H類型實(shí)例的指針, vtable是一個(gè)指向?qū)嶋H類型對(duì)于該trait的實(shí)現(xiàn)的虛函數(shù)表:

Foo的虛函數(shù)表類型:

struct FooVtable {
    destructor: fn(*mut ()),
    size: usize,
    align: usize,
    method: fn(*const ()) -> String,
}

之前的代碼可以解讀為:

// u8:
// 這個(gè)函數(shù)只會(huì)被指向u8的指針調(diào)用
fn call_method_on_u8(x: *const ()) -> String {
    let byte: &u8 = unsafe { &*(x as *const u8) };

    byte.method()
}

static Foo_for_u8_vtable: FooVtable = FooVtable {
    destructor: /* compiler magic */,
    size: 1,
    align: 1,

    method: call_method_on_u8 as fn(*const ()) -> String,
};

// String:
// 這個(gè)函數(shù)只會(huì)被指向String的指針調(diào)用
fn call_method_on_String(x: *const ()) -> String {
    let string: &String = unsafe { &*(x as *const String) };

    string.method()
}

static Foo_for_String_vtable: FooVtable = FooVtable {
    destructor: /* compiler magic */,
    size: 24,
    align: 8,

    method: call_method_on_String as fn(*const ()) -> String,
};

let a: String = "foo".to_string();
let x: u8 = 1;

// let b: &Foo = &a;
let b = TraitObject {
    // data存儲(chǔ)實(shí)際值的引用
    data: &a,
    // vtable存儲(chǔ)實(shí)際類型實(shí)現(xiàn)Foo的方法
    vtable: &Foo_for_String_vtable
};

// let y: &Foo = x;
let y = TraitObject {
    data: &x,
    vtable: &Foo_for_u8_vtable
};

// b.method();
(b.vtable.method)(b.data);

// y.method();
(y.vtable.method)(y.data);

對(duì)象安全

并不是所有的trait都能作為trait對(duì)象使用的,比如:

let v = vec![1, 2, 3];
let o = &v as &Clone;

會(huì)有一個(gè)錯(cuò)誤:

error: cannot convert to a trait object because trait `core::clone::Clone` is not object-safe [E0038]
let o = &v as &Clone;
        ^~
note: the trait cannot require that `Self : Sized`
let o = &v as &Clone;
        ^~

讓我來(lái)分析一下錯(cuò)誤的原因:

pub trait Clone: Sized {
    fn clone(&self) -> Self;

    fn clone_from(&mut self, source: &Self) { ... }
}

雖然Clone本身集成了Sized這個(gè)trait,但是它的方法fn clone(&self) -> Selffn clone_from(&mut self, source: &Self) { ... }含有Self類型,而在使用trait對(duì)象方法的時(shí)候Rust是動(dòng)態(tài)派發(fā)的,我們根本不知道這個(gè)trait對(duì)象的實(shí)際類型,它可以是任何一個(gè)實(shí)現(xiàn)了該trait的類型的值,所以Self在這里的大小不是Self: Sized的,這樣的情況在Rust中被稱為object-unsafe或者not object-safe,這樣的trait是不能成為trait對(duì)象的。

總結(jié):

如果一個(gè)trait方法是object safe的,它需要滿足:

  • 方法有Self: Sized約束, 或者
  • 同時(shí)滿足以下所有條件:
    • 沒(méi)有泛型參數(shù)
    • 不是靜態(tài)函數(shù)
    • 除了self之外的其它參數(shù)和返回值不能使用Self類型

如果一個(gè)traitobject-safe的,它需要滿足:

  • 所有的方法都是object-safe的,并且
  • trait 不要求 Self: Sized 約束

參考stackoverflow object safe rfc