10玩rust_有趣的 Rust 类型系统: Trait
也許你已經學習了標準庫提供的 String 類型,這是一個 UTF-8 編碼的可增長字符串。該類型的結構為:
pub struct String {vec: Vec<u8>,
}UTF-8 的特性決定了你無法索引 String:
let s = "hello";println!("The first letter of s is {}", s[0]); // ERROR!!!雖然你能切片 String,但對非 ascii 字符,切片是非常危險的,你很難確定邊界在哪里。
fn main() {let s = "玩轉 Rust 類型系統".to_string();println!("{:?}", &s[7..11]); // Rustprintln!("{:?}", &s[12..13]); // thread 'main' panicked at 'byte index 13 is not a char boundary; it is inside '類' (bytes 12..15) of `玩轉 Rust 類型系統`', src/main.rs:5:23
}怎么辦? 你可以自己造一個支持索引、切片安全的字符串類型。Rust 的類型系統允許你這么做。
你可以將 String 內部的 Vec<u8> 換成 Vec<char>,再實現一下 From 這個 tarit。該 tarit 用于類型轉換,這里是將 &str 轉換為 MyString:
#[derive(Debug)]
pub struct MyString {vec: Vec<char>
}impl<'a> From<&'a str> for MyString {fn from(string: &'a str) -> Self {Self {vec: string.chars().collect()}}
}這時候你已經可以玩耍你的 MyString 了。
fn main() {let s: MyString = MyString::from("玩轉 Rust 類型系統");println!("{:?}", s); // MyString { vec: ['也', '說', ' ', 'R', 'u', 's', 't', ' ', '類', '型', '系', '統'] }
}上面的 debug 輸出可能并不是你想要的,你可以自己實現 Debug:
use std::fmt;pub struct MyString {vec: Vec<char>
}impl fmt::Debug for MyString {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {for c in &self.vec {f.write_fmt(format_args!("{}", c))?;}Ok(())}
}這時候再次運行,可能會輸出你想要的結果。你可以定制 Debug 的輸出結果:
for (i, c) in self.vec.iter().enumerate() {f.write_fmt(format_args!("{}:{}, ", i, c))?;
}上面代碼將會輸出:
0:也, 1:說, 2: , 3:R, 4:u, 5:s, 6:t, 7: , 8:類, 9:型, 10:系, 11:統,得益于 Rust 的宏系統,你可以簡寫上面的一部分:
for (i, c) in self.vec.iter().enumerate() {write!(f, "{}:{}, ", i, c)?;
}write! 會幫你調用 write_fmt,write_fmt 通常來自 fmt::Write 和 io::Write。比如說,Stdout 實現了 io::Write,你可以用 write! 將字符“寫”到標準輸出:
fn main() {use std::io::Write;write!(std::io::stdout(), "{:?}n", "玩轉 Rust 類型系統").unwrap(); // "玩轉 Rust 類型系統"
}知機識變,得心應手
如果你現在 MyString 所在的 crate 的目錄中執行 cargo doc --open 查看 MyString 的文檔,會發現除了你上面實現的 impl Debug for MyString 和 impl<'a> From<&'a str> for MyString,在 “Blanket Implementations” 這一節中編譯器還幫你“一攬子實現”了幾個 Tarit。我們現在感興趣的是:
impl<T, U> Into<U> for T
whereU: From<T>,
{fn into(self) -> U {U::from(self)}
}Into 是幫助你進行類型轉換的。你可以改一下上面創建 MyString 的代碼:
fn main() {// let s: MyString = MyString::from("玩轉 Rust 類型系統");let s: MyString = "玩轉 Rust 類型系統".into();println!("{:?}", s); // 玩轉 Rust 類型系統
}Into 在某些時候非常有用,比如我想實現一個能容納多種類型的容器:
#[derive(Debug)]
pub enum Value {F32(f32),I32(i32),String(String)
}#[derive(Debug)]
pub struct MyColl {vec: Vec<Value>
}impl MyColl {pub fn new() -> Self {Self {vec: Vec::new()}}pub fn push(&mut self, v: impl Into<Value>) {self.vec.push(v.into())}
}impl From<f32> for Value {fn from(f: f32) -> Value {Value::F32(f)}
}impl From<i32> for Value {fn from(i: i32) -> Value {Value::I32(i)}
}impl From<String> for Value {fn from(s: String) -> Value {Value::String(s)}
}向該集合 push 元素時,達到了類似于動態語言的效果:
fn main() {let mut coll = MyColl::new();coll.push(123i32);coll.push(456f32);coll.push("hello".to_string());
}與 From 類似的還有 FromStr。標準庫為一些類型實現了 FromStr,比如:
impl FromStr for f32
impl FromStr for Ipv4Addr借助于 FromStr,你可以方便的將字符串轉換為你需要的類型:
use std::str::FromStr;
use std::net::Ipv4Addr;fn main() {let f = f32::from_str("1.23").unwrap();println!("{:?}", f); // 1.23let addr = Ipv4Addr::from_str("127.0.0.1").unwrap();println!("{:?}", addr); // 127.0.0.1
}就這? 下面這段代碼才是點睛之筆:
impl str {...pub fn parse<F: FromStr>(&self) -> Result<F, F::Err> {FromStr::from_str(self)}...
}有了 parse,你就可以:
use std::net::Ipv4Addr;fn main() {let f: f32 = "1.23".parse().unwrap();println!("{:?}", f); // 1.23let addr: Ipv4Addr = "127.0.0.1".parse().unwrap();println!("{:?}", addr); // 127.0.0.1
}需要說明的是,From, Into, FromStr 和 parse 都是在庫的層面實現了,并沒有在編譯層面“打洞”。掌握了這些,你可以舉一反三構建你自己的類型體系。
機由己發,力從人借
我們把目光轉移到 MyString,為 MyString 實現 Deref 和 DerefMut 這兩個 tarit:
use std::ops::{Deref, DerefMut};impl Deref for MyString {type Target = Vec<char>;fn deref(&self) -> &Self::Target {&self.vec}
}impl DerefMut for MyString {fn deref_mut(&mut self) -> &mut Self::Target {&mut self.vec}
}執行 cargo doc 后你會發現 Methods from Deref<Target = Vec<char>> 這一節,像是從 Vec “繼承”了一些方法。不過 Rust 可沒有繼承,這也是利用 tarit。
fn main() {let mut s: MyString = "玩轉 Rust 類型系統".into();println!("{:?}", s); // 玩轉 Rust 類型系統println!("{:?}", s[0]); // 玩println!("{:?}", s[9]); // 型println!("{:?}", &s[3..7]); // ['R', 'u', 's', 't']s[0] = '哇';println!("{:?}", s); // 哇轉 Rust 類型系統
}現在你已經可以索引和切片 MyString,如果你對從 Vec “借”來的東西不滿意,可以自己去實現。
他山之石,可以攻玉
我們再為 MyString 實現一下代器(Iterator)。要實現 Iterator 非常簡單,
trait Iterator {type Item;fn next(&mut self) -> Option<Self::Item>;
}只需要實現 next 方法即可。這里還需要一個變量去記錄迭代位置。
impl MyString {pub fn iter(&self) -> Iter {Iter {s: &self.vec,pos: 0}}
}use std::iter::{Iterator, IntoIterator};pub struct Iter<'a> {s: &'a Vec<char>,pos: usize
}impl<'a> Iterator for Iter<'a> {type Item = &'a char;fn next(&mut self) -> Option<Self::Item> {self.s.get(self.pos).map(|c| { self.pos += 1; c })}
}現在你就可以遍歷字符串了:
for c in s.iter() {println!("{:?}", c);
}如果你想使用 for c in &s 這種形式,還需要為 MyString 實現 IntoIterator 這個 trait:
impl<'a> IntoIterator for &'a MyString {type Item = &'a char;type IntoIter = Iter<'a>;fn into_iter(self) -> Self::IntoIter {self.iter()}
}不過,你也可以“借用” slice 的 Iter。由于 IterMut 和 IntoIter 實現復雜,也可以借來一用。
Iter,IterMut和IntoIter的命名時約定俗成的。
最終的代碼為:
impl MyString {pub fn iter(&self) -> std::slice::Iter<char> {self.vec.iter()}pub fn iter_mut(&mut self) -> std::slice::IterMut<char> {self.vec.iter_mut()}pub fn into_iter(self) -> std::vec::IntoIter<char> {self.vec.into_iter()}
}use std::iter::{Iterator, IntoIterator};impl<'a> IntoIterator for &'a MyString {type Item = &'a char;type IntoIter = std::slice::Iter<'a, char>;fn into_iter(self) -> Self::IntoIter {self.vec.iter()}
}impl<'a> IntoIterator for &'a mut MyString {type Item = &'a mut char;type IntoIter = std::slice::IterMut<'a, char>;fn into_iter(self) -> Self::IntoIter {self.vec.iter_mut()}
}impl IntoIterator for MyString {type Item = char;type IntoIter = std::vec::IntoIter<char>;fn into_iter(self) -> Self::IntoIter {self.vec.into_iter()}
}現在你可以用下面三種方式遍歷 MyString:
for c in &s {println!("{:?}", c);
}for c in &mut s {println!("{:?}", c);
}for c in s {println!("{:?}", c);
}這就完了?并沒有!這一節的重點并不是教你如何實現 Iterator,而是想說 Iterator 下面有眾多方法:
pub trait Iterator {type Item;fn next(&mut self) -> Option<Self::Item>;fn size_hint(&self) -> (usize, Option<usize>) { ... }fn count(self) -> usize { ... }fn last(self) -> Option<Self::Item> { ... }fn advance_by(&mut self, n: usize) -> Result<(), usize> { ... }fn nth(&mut self, n: usize) -> Option<Self::Item> { ... }fn step_by(self, step: usize) -> StepBy<Self> { ... }fn chain<U>(self, other: U) -> Chain<Self, <U as IntoIterator>::IntoIter>whereU: IntoIterator<Item = Self::Item>,{ ... }fn zip<U>(self, other: U) -> Zip<Self, <U as IntoIterator>::IntoIter>?whereU: IntoIterator,{ ... }fn map<B, F>(self, f: F) -> Map<Self, F>?whereF: FnMut(Self::Item) -> B,{ ... }fn for_each<F>(self, f: F)whereF: FnMut(Self::Item),{ ... }...你只要實現 next 方法,就可以免費、無償使用這些方法。與 Iterator 類似的還有 io::Write 和 io::Read 等。
這種思路雖然在其他語言中也經常見到,但在 Rust 中的實現是非常清晰明了的。
trait Log {fn print(&self, s: &str);fn info(&self, s: &str) {self.print(&format!("INFO: {}", s))}fn debug(&self, s: &str) {self.print(&format!("DEBUG: {}", s))}fn error(&self, s: &str) {self.print(&format!("ERROR: {}", s))}
}struct MyPrint;impl Log for MyPrint {fn print(&self, s: &str) {println!("{}", s);}
}fn main() {let print = MyPrint;print.info("hello"); // INFO: helloprint.debug("hello"); // DEBUG: helloprint.error("hello"); // ERROR: hello
}上面的代碼演示了如何實現一個簡易的日志組件。
本文到這里就結束了。我并不想把 trait 的功能一一列舉一遍,而是以實現 MyString 為導向,選取了幾個比較有趣的例子。
總結
以上是生活随笔為你收集整理的10玩rust_有趣的 Rust 类型系统: Trait的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 北京开拓顶点钓鱼灯DL90专用充电器多少
- 下一篇: 讯飞语音识别_讯飞输入法持续功能创新 语