Rust 入门笔记【五】
Result
大部分的错误并没有严重到需要程序完全停止。来看下面的例子。
use std::fs::File;
fn main() {
let hello = File::open("hello.txt");
}
open函数的功能是打开一个文件,其返回值就是一个
enum Result<T,E> {
Ok(T),
Err(E),
}
这样我们可以利用match来对不同的结果做不同的处理。
use std::fs::File;
fn main() {
let hello = File::open("hello.txt");
let hello = match hello {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error),
};
}
但是,如果发生了错误,无论什么错误都会放到Err中,所以我们其实可以对Err在进行match。比如这里如果是找不到文件就创建一个文件。
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let hello = File::open("hello.txt");
let hello = match hello {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
_ => panic!("Problem opening the file: {:?}", error),
},
};
}
match可以处理这种返回值为枚举类型的情况,但是太复杂了。比如上面的例子我们首先区分了是不是Err,如果是Err又区分了NotFound和其他错误,如果是NotFound我们还要创建文件,创建文件也可能会失败。
因此我们需要一种简写的方法。
use std::fs::File;
fn main() {
let hello = File::open("hello.txt").unwrap();
}
unwrap函数,如果是Ok(file)的话就会返回file,如果是Err(error)的话就用调用panic打印error。
use std::fs::File;
fn main() {
let hello = File::open("hello.txt").expect("Failed to open ... hello.txt");
}
expect与unwarp的用法一样,只是如果是Error,报错信息是我们提供的。
传播错误
当我们在函数执行时,如果遇到了错误,有些时候并不需要我们自己处理,而是需要把错误信息返回,交给函数调用者来处理。
use std::fs::File;
use std::io::{self, Read};
fn read_file(s: &str) -> Result<String, io::Error> {
let file = File::open(s);
let mut file = match file {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut text = String::new();
match file.read_to_string(&mut text) {
Ok(_) => Ok(text),
Err(e) => Err(e),
}
}
fn main() {
let text = read_file("hello.txt").unwrap();
println!("{text}");
}
这个函数就是从文件中读取字符串,如果读取失败就返回错误信息。这个代码还是太冗余了,rust提供了一个简写的方法。
use std::fs::File;
use std::io::{self, Read};
fn read_file(s: &str) -> Result<String, io::Error> {
let mut file = File::open(s)?;
let mut text = String::new();
file.read_to_string(&mut text)?;
return Ok(text);
}
fn main() {
let text = read_file("hello.txt").unwrap();
println!("{text}");
}
这个代码和刚才的代码功能完全一样。?的功能是如果Result的类型是Err就结束当前函数返回Err(error)。注意的是?会自动进行类型匹配。比如这里的open的返回值Result<File,io::Error>::Err,如果用?返回值实际上是Result<String,io::Error>::Err,也就是我们不懂担心类型不配的问题。
甚至对于?我们可以进行链式调用。
use std::fs::File;
use std::io::{self, Read};
fn read_file(s: &str) -> Result<String, io::Error> {
let mut text = String::new();
File::open(s)?.read_to_string(&mut text)?;
return Ok(text);
}
fn main() {
let text = read_file("hello.txt").unwrap();
println!("{text}");
}
泛型函数
fn largest<T>(list: &[T]) -> &T {
let mut val = &list[0];
for item in list {
if (item > val) {
val = item;
}
}
val
}
这是一个简单的泛型函数,但是这个函数并不能通过编译。是因为并不是所有的类型都有>,如果一个类型没有>但是确传入到这个函数就会错误。rust认为这是不安全的所有不允许编译通过。
泛型结构体
struct Point<T> {
x: T,
y: T,
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: 1.3, y: 2.4 };
}
在创建对象时,如果已经给定了实参,编译器就可以自动推断类型。
泛型枚举
Option<T>就是一个很经典的泛型。
enum Option<T> {
Some(T),
None,
}
泛型成员函数
成员函数也可以是泛型的,因为泛型,所有只要求格式一致就好了。
struct Point<T> {
x: T,
y: T,
}
impl<U> Point<U> {
fn x(&self) -> &U {
&self.x
}
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let x1 = p1.x();
}
并且泛型成员函数也可以调用其他的泛型对象。比如
struct Point<T, U> {
x: T,
y: U,
}
impl<X1, Y1> Point<X1, Y1> {
fn mix<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
Point {
x: self.x,
y: other.y,
}
}
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: "1", y: "2" };
let p3 = p1.mix(p2);
}
trait
一个类的行为由它的可调用方法构成,如果不同的类有相同的可构造方法,这些类就共享了相同的行为。
trait就是用来定义共享行为集合的。
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct News {
pub title: String,
pub body: String,
pub author: String,
}
pub struct Tweet {
pub text: String,
pub url: String,
pub author: String,
pub title: String,
}
impl Summary for News {
fn summarize(&self) -> String {
return format!("{}: {}", self.title, self.author);
}
}
impl Summary for Tweet {
fn summarize(&self) -> String {
return format!("{}: {}", self.title, self.author);
}
}
这样两个不同的类就实现两个名字和功能相同的函数。但是这样本质上和分别声明两个函数没有区别。所以我们需要给函数实现一个默认实现。
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
pub struct News {
pub title: String,
pub body: String,
pub author: String,
}
pub struct Tweet {
pub text: String,
pub url: String,
pub author: String,
pub title: String,
}
impl Summary for News {}
impl Summary for Tweet {}
这样如果在impl中实现了summarize就会对默认函数重写,否则就会使用默认实现。
trait 作为参数
对于刚才的例子,我们把把trait作为一个参数。
fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
这个函数可以接受任意一个实现了Summary的对象。
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
pub struct News {
pub title: String,
pub body: String,
pub author: String,
}
pub struct Tweet {
pub text: String,
pub url: String,
pub author: String,
pub title: String,
}
impl Summary for News {}
impl Summary for Tweet {}
fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
fn main() {
let n = News {
title: String::from("title 1"),
body: String::from("body 1"),
author: String::from("author 1"),
};
let t = Tweet {
title: String::from("title 2"),
text: String::from("text 2"),
url: String::from("url 2"),
author: String::from("author 2"),
};
n.summarize();
t.summarize();
notify(&n);
notify(&t);
}
trait bound
还有一种更加灵活的方法是结合模板来实现。
fn notify<T: Summary>(item: &T) {}
如果要接受两个变量的话,可以这样声明
fn notify<T: Summary>(item1: &T, item2: &T) {}
这里要求的是两个变量的类型必须相同,其实不同也是可以的。
fn notify<T:Summary, U:Summary>(item1: &T, item2: &U) {}
如果要求多个trait bound可以如下方法
trait a {
fn f() -> () {}
}
trait b {
fn g() -> () {}
}
struct S {}
impl a for S {}
impl b for S {}
struct P {}
impl a for P {}
struct Q {}
impl b for Q {}
fn foo<T: a + b>(x: &T) {}
fn main() {
let s = S {};
let p = P {};
let q = Q {};
foo(&s);
// foo(&p);
// foo(&q);
}
还有一个情况是,如果传入的参数很多,且每个参数都有多个trait bound,则定义的行就会很长,因此rust支持用where从句来实现。
fn foo<T, U>(item1: T, item2: U, item3: U) -> T
where
T: a + b,
U: a + b,
{
item1
}3
返回值实现 trait
trait a {
fn f() -> () {}
}
struct S {}
impl a for S {}
fn f() -> impl a {
S{}
}
注意返回值是不能使用trait bound,如果你写了如下的代码
fn f<T: a>() -> T {
S{}
}
是无法编译的,因为函数的返回值要求是T,但你却返回了S可能会造成类型不匹配的情况。
使用trait bound 有条件的实现函数。
use std::fmt::Display;
struct Pair<T>{
x: T,
y: T,
}
impl <T> Pair<T> {
fn new(x:T ,y:T) -> Pair<T>{
Pair{x,y}
}
}
impl <T: Display + PartialOrd> Pair<T> {
fn display_max(&self){
if self.x > self.y {
println!("{}",self.x);
} else {
println!("{}",self.y);
}
}
}
fn main() {}
这里实现了一个Pair,对任意类型均实现了构造函数,只对可以比较和输出的实现了display_max。
对于任意能满足trait bound 的类型实现trait你的函数被称为blanket implementations,这种方法被广泛的引用到rust中去。所以我们便可以根据这个blanket implementations自定义各种泛型的操作。

浙公网安备 33010602011771号