Trait Impl/Bound: 特设多态,编译期单态化

Trait impl的两面派

其实从两个相对的角色🎭来看,就可以理解为何trait impl有两种叫法。

具体实现

当一个开发者想给类型添加方法的时候,需要先定义trait,再为类型实现trait指定方法。

专门对比一下impl和trait impl

  1. impl更加自由,可以impl TypeName这样的方式给类型定义任意方法
  2. impl for 这样的方式限定实现指定接口方法

设计约束

当一个设计者设计好完整流程后,为了让使用者能够保留一定空间自由度去自定义实现,就可以要求这个类型需要满足某个trait,这就是trait约束

基本练习: 使用self参数

支持泛型

版本一:支持数字相加

use std::ops::Add;

#[derive(Debug)]
struct Complex {
    real: f64,
    imagine: f64,
}

impl Complex {
    pub fn new(real: f64, imagine: f64) -> Self {
        Self { real, imagine }
    }
}

// 对 Complex 类型的实现
impl Add for Complex {
    type Output = Self;

    // 注意 add 第一个参数是 self,会移动所有权
    fn add(self, rhs: Self) -> Self::Output {
        let real = self.real + rhs.real;
        let imagine = self.imagine + rhs.imagine;
        Self::new(real, imagine)
    }
}


fn main() {
    let c1 = Complex::new(1.0, 1f64);
    let c2 = Complex::new(2 as f64, 3.0);
    println!("{:?}", c1 + c2);
    // c1, c2 已经被移动,所以下面这句无法编译
    // println!("{:?}", c1 + c2);
}

支持继承

trait B:A

在 Rust 中,一个 trait 可以“继承”另一个 trait 的关联类型和关联函数。比如 trait B: A ,是说任何类型 T,如果实现了 trait B,它也必须实现 trait A,换句话说,trait B 在定义时可以使用 trait A 中的关联类型和方法。

impl<T: ?Sized> StreamExt for T where T: Stream {}

如果你实现了 Stream trait,就可以直接使用 StreamExt 里的方法了

Self和self

类比python:Self对应Cls, self两边一样。

在闭包中还要结合考虑是否转移所有权:

  • self转移
  • Self不转移

Self和self区别使用, Self其实就是类方法

use std::fmt;
use std::io::Write;

struct BufBuilder {
    buf: Vec<u8>,
}

impl BufBuilder {
    pub fn new() -> Self {
        Self {
            buf: Vec::with_capacity(1024),
        }
    }
}

impl fmt::Debug for BufBuilder {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", String::from_utf8_lossy(self.buf.as_ref()))
    }
}

impl Write for BufBuilder {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        self.buf.extend_from_slice(buf);
        Ok(buf.len())
    }

    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}

fn main() {
    let mut buf = BufBuilder::new();
    buf.write_all(b"Hello world!").unwrap();
    println!("{:?}", buf);
}

顺便区分一下类方法和静态方法

  1. 定义时:类方法需要加cls参数,静态方法不需要
  2. 调用时:类方法默认添加cls参数, 静态方法不会传入

self: Self, 实例来自于类型

  1. Self 代表当前的类型,比如 File 类型实现了 Write,那么实现过程中使用到的 Self 就指代 File。
  2. self 在用作方法的第一个参数时,实际上是 self: Self 的简写,所以 &self 是 self: &Self, 而 &mut self 是 self: &mut Self。

递进练习trait使用场景

基础使用:具体类型实现

定义Parse trait并实现使用

use regex::Regex;
pub trait Parse {
    fn parse(s: &str) -> Self;
}

impl Parse for u8 {
    fn parse(s: &str) -> Self {
        let re: Regex = Regex::new(r"^[0-9]+").unwrap();
        if let Some(captures) = re.captures(s) {
            captures
                .get(0)
                .map_or(0, |s| s.as_str().parse().unwrap_or(0))
        } else {
            0
        }
    }
}

#[test]
fn parse_should_work() {
    assert_eq!(u8::parse("123abcd"), 123);
    assert_eq!(u8::parse("1234abcd"), 0);
    assert_eq!(u8::parse("abcd"), 0);
}

fn main() {
    println!("result: {}", u8::parse("255 hello world"));
}

  1. 这里的Parse Trait里面的parse方法没有传入self/Self参数,所以调用的时候使用::而不是.
  2. 这种基础用法中,被实现的是具体类型

进阶使用

  1. 这也是分离定义与实现的用处
  2. 下方的常用trait实现也是基于进阶使用整理出来提供的工具。

泛型实现+trait约束

impl Parse for T

use std::str::FromStr;

use regex::Regex;
pub trait Parse {
    fn parse(s: &str) -> Self;
}

impl<T> Parse for T
where
    T: FromStr + Default,
{
    fn parse(s: &str) -> Self {
        let re: Regex = Regex::new(r"^[0-9]+(\.[0-9]+)?").unwrap();
        let d = || Default::default();
        if let Some(captures) = re.captures(s) {
            captures
                .get(0)
                .map_or(d(), |s| s.as_str().parse().unwrap_or_else(|_| d()))
        } else {
            d()
        }
    }
}

#[test]
fn parse_should_work() {
    assert_eq!(u32::parse("123abcd"), 123);
    assert_eq!(u32::parse("123.45abcd"), 0);
    assert_eq!(f64::parse("123.45abcd").to_string(), "123.45");
    assert_eq!(f64::parse("abcd").to_string(), "0");
}

fn main() {
    println!("result: {}", u8::parse("255 hello world"));
}
  1. 对比上一个例子,这里被实现的是泛型,对于上一种用法进一步抽象
  2. 这样就把被实现类型从一个具体类型,扩展为一类实现了具体trait的类型,不需要重复去实现trait

这个抽象点多体会一下:从抓类型到抓实现特定trait的泛型

trait带有泛型参数+trait约束

使用一个思考题来加深印象

泛型参数impl报错

use std::io::{BufWriter, Write};
use std::net::TcpStream;

#[derive(Debug)]
struct MyWriter<W> {
    writer: W,
}

impl<W: Write> MyWriter<W> {
    pub fn new(addr: &str) -> Self {
        let stream = TcpStream::connect("127.0.0.1:8080").unwrap();
        Self {
            writer: BufWriter::new(stream),
        }
    }

    pub fn write(&mut self, buf: &str) -> std::io::Result<()> {
        self.writer.write_all(buf.as_bytes())
    }
}

fn main() {
    let writer = MyWriter::new("127.0.0.1:8080");
    writer.write("hello world!");
}

分析编译报错原因

主要原因是,实现 new 方法时,对泛型的约束要求要满足 W: Write,而 new 的声明返回值是 Self,也就是说 self.wirter 必须是 W: Write 类型(泛型),但实际返回值是一个确定的类型 BufWriter,这不满足要求。

解决方案梳理

  1. 修改 new 方法的返回值
  2. 对确定的类型 MyWriter<BufWriter>实现 new 方法
  3. 修改 new 方法的实现,使用依赖注入
  1. 修改new方法返回值

use std::io::{BufWriter, Write};
use std::net::TcpStream;

#[derive(Debug)]
struct MyWriter<W> {
    writer: W,
}

impl<W: Write> MyWriter<W> {
    pub fn new(writer: W) -> Self {
        Self { writer }
    }

    pub fn write(&mut self, buf: &str) -> std::io::Result<()> {
        self.writer.write_all(buf.as_bytes())
    }
}

fn main() {
    let stream = TcpStream::connect("127.0.0.1:8080").unwrap();

    let mut writer = MyWriter::new(BufWriter::new(stream));
    writer.write("hello world!").unwrap();
}
  1. 针对实现new方法

impl MyWriter<BufWriter<TcpStream>> {
    pub fn new(addr: &str) -> Self {
        let stream = TcpStream::connect(addr).unwrap();
        Self {
            writer: BufWriter::new(stream),
        }
    }
}

fn main() {
    let mut writer = MyWriter::new("127.0.0.1:8080");
    writer.write("hello world!");
}
  1. 使用依赖注入修改new方法实现

impl<W: Write> MyWriter<W> {
    pub fn new(writer: W) -> Self {
        Self {
            writer,
        }
    }
}

fn main() {
    let stream = TcpStream::connect("127.0.0.1:8080").unwrap();
    let mut writer = MyWriter::new(BufWriter::new(stream));
    writer.write("hello world!");
}

补充使用:使用关联类型+添加Result<T, E>

关联类型自定义Error

use std::str::FromStr;

use regex::Regex;
pub trait Parse {
    type Error;
    fn parse(s: &str) -> Result<Self, Self::Error>
    where
        Self: Sized;
}

impl<T> Parse for T
where
    T: FromStr + Default,
{
    // 定义关联类型 Error 为 String
    type Error = String;
    fn parse(s: &str) -> Result<Self, Self::Error> {
        let re: Regex = Regex::new(r"^[0-9]+(\.[0-9]+)?").unwrap();
        if let Some(captures) = re.captures(s) {
            // 当出错时我们返回 Err(String)
            captures
                .get(0)
                .map_or(Err("failed to capture".to_string()), |s| {
                    s.as_str()
                        .parse()
                        .map_err(|_err| "failed to parse captured string".to_string())
                })
        } else {
            Err("failed to parse string".to_string())
        }
    }
}

#[test]
fn parse_should_work() {
    assert_eq!(u32::parse("123abcd"), Ok(123));
    assert_eq!(
        u32::parse("123.45abcd"),
        Err("failed to parse captured string".into())
    );
    assert_eq!(f64::parse("123.45abcd"), Ok(123.45));
    assert!(f64::parse("abcd").is_err());
}

fn main() {
    println!("result: {:?}", u8::parse("255 hello world"));
}

#![allow(unused)]
fn main() {
type Error = String;
fn parse(s: &str) -> Result<Self, Self::Error>
}