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");
}

expectunwarp的用法一样,只是如果是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自定义各种泛型的操作。

posted @ 2025-05-05 17:46  PHarr  阅读(32)  评论(0)    收藏  举报