rust泛型学习实例

  • 2022年 1月16日
  • 读完约需 6 分钟
  • • 
  • 标签: 
  • rust
  • 最后更新于 2022年 1月16日

这是程序君的 Rust 培训(1)中关于泛型的练习,教程中作者使用了 Vec<u8>作为 encode() 的返回值,练习时自己做了修改,支持泛型。

首先来看 trait 中泛型的定义:

pub  trait  Encoder {
	type  Target;
	fn  encode(&self) -> Result<Vec<Self::Target>, Error>;
}

encode() 函数返回一个 Vector<T>,有中文翻译为可变长数组。T 是type Target所定义的类型,这就是典型的泛型。T 此时不指向任何具体类型,需要在使用时,通过 Target 来确定,比如:

	impl  Encoder  for  String {
		type  Target = u8;  // 定义T为u8类型
		fn  encode(&self) -> Result<Vec<Self::Target>, Error> {
			let  ret = self.as_bytes().to_vec();
			Ok(ret) // 返回Vector<u8>
		}
	}

这里定义了type Target = u8;,那么任何 String 类型调用 encode() ,就会返回一个 Vec<u8>。

接下来看一个更复杂的例子:

pub  struct User<Id, Data>   
{ 
	id: Id, 
	data: Data,  
}
impl<Id, Data> User<Id, Data>
{
	pub  fn  new(id: Id, data: Data) ->  Self {
		Self { id, data }
	}
}

User 结构定义中,定义了两个泛型参数 Id 和 Data,接着往下看:

impl<Id, Data, U> Encoder  for  User<Id, Data, U>
where
	Id: Encoder<Target = U>,
	Data: Encoder<Target = U>,
{
	type  Target = U;
	fn  encode(&self) -> Result<Vec<Self::Target>, Error> {
		let  mut  ret = self.id.encode()?;
		ret.append(&mut  self.data.encode()?);
		Ok(ret)
	}
}

这里的不同在于,为 User 实现 Encoder trait 的时候,额外定义了泛型参数 U,它有两个作用:

  1. 从约束定义看,它定义了 Id 和 Data 参数的约束,要求二者都实现了 Encoder trait,且两者的 encode() 函数的返回结果都是同一类型(U)。
  2. type Target = U,定义了 User 的 encode() 返回结果也是类型 U。

通过约束和泛型参数,对出入参的类型及它们之间的关系进行了正确的约束:即不论 Id 和 Data 是何具体类型,只要它们实现了 Encoder trait,并且 encode() 的返回结果为同一类型,就能保证 user 的 encode() 的返回结果有效,且一定是同一类型。

如何使用上述泛型定义呢? 首先,需要确定泛型参数 U 的具体类型。视频中作者使用了: type Target = u8,那么我们需要分别为 Id 和 Data 定义 Target 为 u8 的 Encoder trait:

	impl  Encoder  for  u64 {
		type  Target = u8;
		fn  encode(&self) -> Result<Vec<Self::Target>, Error> {
			let  ret = self.to_le_bytes().to_vec();
			Ok(ret)
		}
	}
	impl  Encoder  for  String {
		type  Target = u8;
		fn  encode(&self) -> Result<Vec<Self::Target>, Error> {
			let  ret = self.as_bytes().to_vec();
			Ok(ret)
		}
	}

此时,User 调用 encode() 就能得到期望的结果了。

let  t1 = User::new(1, "abcdef".to_string());
println!("t1: {:?}",t1.encode());
// 会输出 t1: [1, 0, 0, 0, 0, 0, 0, 0, 97, 98, 99, 100, 101, 102]

泛型的使用,对于提炼程序逻辑,做到数据逻辑分离有巨大的帮助。但是需要把握抽象粒度,不同的应用场景需要不同的抽象粒度。比如上述,我考虑过把整个 Vec<Self::Target> 替换成 Self::Target,但是实现 User 的 encode() 时遇到了问题,我没找到合适的方法来“连接” id 和 data 的 encode()结果,后续如果有解决方案,会在这说明。

解决方法来自 rust 中文社区大神 uno 的回帖 具体方法如下:

  1. 定义 Append trait,实现 append()
  2. User 实现 Encoder trait 时,对 U 增加约束:U: Append,
  3. test 中为 Vec<u8> 实现这个trait。
pub  trait  Append {
	fn  append(&mut  self, data: &mut  Self);
}

......

impl  Append  for  Vec<u8> {
	fn  append(&mut  self, data: &mut  Self) {
		self.append(&mut  *data);
	}
}

整个代码如下:

use std::io::Error;

pub trait Append {
    fn append(&mut self, data: &mut Self);
}
pub trait Encoder {
    type Target;
    fn encode(&self) -> Result<Self::Target, Error>;
}

#[derive(Debug)]
pub struct User<Id, Data>
{
    id: Id,
    data: Data,
}

impl<Id, Data> User<Id, Data>
{
    pub fn new(id: Id, data: Data) -> Self {
        Self { id, data }
    }
}

impl<Id, Data, T> Encoder for User<Id, Data>
where
    Id: Encoder<Target = T>,
    Data: Encoder<Target = T>,
    T: Append,
{
    type Target = T;
    fn encode(&self) -> Result<Self::Target, Error> {
        let mut ret = self.id.encode()?;
        // println!("ret.0 is {:?}",ret.0);
        ret.append(&mut self.data.encode()?);
        Ok(ret)
    }
}


#[cfg(test)]
mod tests {
    use super::*;

    impl Append for Vec<u8> {
        fn append(&mut self, data: &mut Self) {
            self.append(&mut *data);
        }
    }
    
    impl Encoder for u64 {
        type Target = Vec<u8>;
        fn encode(&self) -> Result<Self::Target, Error> {
            let ret = self.to_le_bytes().to_vec();
            Ok(ret)        
        }
    }
    
    impl Encoder for String {
        type Target = Vec<u8>;
        fn encode(&self) -> Result<Self::Target, Error> {
            let ret = self.as_bytes().to_vec();
            Ok(ret)
        }
    }
    #[test]
    fn test_name() {
        let t1 = User::new(1, "abcdef".to_string());
        println!("t1: {:?}", t1.encode().unwrap());
        let t2 = User::new("be".to_string(), 256);
        println!("t2: {:?}", t2.encode());
    }
}