Rust基础学习笔记(一):Generic Types, Traits, and Lifetime

这是通过官方文档学习Rust基础的第一篇博客,也是笔者的第一篇技术博客,还蛮有纪念意义的。

学习Rust的原因是在学习前端的过程中需要一门后端开发语言,经建议准备入手Rust。笔者在写这篇博客之前已经阅读了前九章的内容,然而苦于没有实践,记忆不牢,故打算从第十章开始用博客记录以加深记忆。这一系列大概会以一章一篇的规模更新,阅读完官方文档之后的其他内容也许会脱离这一系列。

第十章内容涉及泛型、特性与生命周期。目的是精简代码结构,基本步骤包括

  1. 找出重复代码;
  2. 在声明中确定接受与返回值的类型,将代码提取到函数体中;
  3. 在主函数中调用该函数。

泛型

我们可以利用泛型来定义函数、类方法、枚举和结构体。

在函数中使用泛型

fn largest<T>(list : &[T]) -> T { ... }

在结构体中使用泛型

struct Point<T> {
x: T,
y: T,
}

使用多个泛型

struct Point<T, U> {
    x: T,
    y: U,
}

在枚举中使用泛型

以Option<T>与Result<T>为例:

enum Option<T> {
    Some(T),
    None,
}

enum Result<T, E>{
    Ok(T),
    Err(E),
}

在类方法中使用泛型

struct Point<T, U>{
    x: T,
    y: U,
}

impl<T, U> Point<T, U>{
    fn  mixup<V, W>(self, other: Point<V, W>) -> Point<T, W>{
        Point {
            x: self.x,
            y: other.y,
            }
        }
}

 

泛型与运行效率

泛型的使用不会降低程序运行的效率,对于Rust而言它只会将泛型替换成确切的形式。这个过程称作Monomorphization

特性

可以与其他类型共享的方法集合,在其它语言中被称为Interface(接口)。

定义一个特性

pub trait Summary{
    fn summarize(&self) -> String;
}

 

 拥有这个特性的类型必须提供具体的函数代码。

在类型中实现特性

pub struct NewsArticle{
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle{
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

 

 限制:只有特性或者类型是本地的时候才能实现。如果两者都位于外部,则不能实现特性。实行这种限制的目的是防止不相关的箱中的代码彼此造成破坏。

默认实现

为特性定义可以重写的默认行为有助于精简代码。

pub trait Summary{
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

impl Summary for NewsArticle {}

 

也可以通过默认行为调用其他的方法。不过这个方法在实现的时候需要具体定义。

pub trait Summary{
    fn summarize_author(&self) -> String;
    fn summarize(&self) -> String {    
        String::from("(Read more from {}...)", summarize_author())
    }
}
impl Summary for tweet{
  fn summarize_author(&self) -> String{
    format!("@{}",&self.username)
  }
}

 

特性做参数

对所有实现了某特性的类型提供统一的函数,并接受这些类型作为参数。在声明时只需要声明被实现的特性即可。

pub fn notify(item :&impl Summary){
    println!("Breaking News! {}", item.summarize());
}

 

使用未实现指定特性的类型做参数时,报错。

特性绑定语法

等同于特性做参数时的用法,而某些时候能精简代码,或者对类型作出限制:

pub fn notify(item1: &impl Summary, item2: &impl Summary) { ... }
//允许两个参数拥有不同类型
pub fn notify<T: Summary> (item1: &T, item2: &T){ ... }
//强制两个参数拥有相同类型

 

用“+”绑定多个特性

同样有两种写法

pub fn notify(item:&(impl Summary + Display)) { ... }
//等同于
pub fn notify<T: Summary + Display>(item: &T) { ... }

Where语法简化特性绑定

提高代码可读性的一种手段

fn some_function<T, U>(item1: &T, item2: &U) -> i32
    where T = Display + Summary,
              U = Clone + Summary
    { ... }

返回Trait

用来返回某种实现了指定特性的类型。主要用在闭包和迭代器里面(以后讨论)

fn ret_impl() -> impl Summary
{
    Tweet{
        ...
        }
}

 

*这个方法只能用来返回单个种类的值,如果出现多种返回值的可能类型,程序不会通过编译。

有条件的实现特性

只会对满足条件的类型添加某种特性:

struct Stru<T>{ ... }

impl<T: Some> Stru<T>{ ... }

//或者选择全部

impl<T: Some> Trait_name for T { ... }

 

 

生命周期

防止垂悬引用

当引用的生命周期长于被引用变量时,垂悬引用报错!

{
    let r;
        {
            let x = 1;
            r = &x;
        }    //x无了
    println!("x = {}" ,r);    //报错,垂悬引用
}

 

生命周期注释

本质上用来绑定返回值的生命周期以保证内存安全

不带注释的时候,编译器不确定引用返回值所在的生命周期,因而报错:

fn longer(str1: &str, str2: &str) ->&str {    
    if str1.len > str.2len {
        str1
    } else {
        str2
    }
}        //报错:返回值在哪个区间里?

 

通过添加生命周期注释可以解决这个问题:

fn longer<'a>(str1: &'a str, str2: &'a str) ->&'a str {    
    if str1.len > str.2len {
        str1
    } else {
        str2
    }
}        //通过编译:特定过的生命周期,选定为&str1和&str2的最小者

 返回值只能在 'a 所指定的范围内生效。

从生命周期的角度理解程序

  • 当某个参数和返回值的生命周期没有关系时不要加注释
  • 返回的引用必须和参数中的至少一者拥有相同的生命周期注释(也可以与函数中声明的变量相同,不过会造成垂悬引用)

在结构体中运用生命周期注释

可以用来确定结构体变量的声明周期:

struct Im<'a>{
    part: &'a str,
}

fn main(){
    let novel = String::from("....");
    let first_sen = novel.spilt('.').next().expect("...");
    let i = Im{
            part : first_sen;
    };
}

 

这里i的声明周期要小于其引用的novel。

被省略的生命周期注释

随着人们发现一些相同的需要注释的情况,这些情况已经在Rust的版本更新中写入源代码,因此可以在一些具体的情况下省略生命周期注释。

随着人们对Rust的开发,需要用到生命周期注释的情况越来越少。

目前有三大类情况可以省略注释:

所有的参数都拥有各自的生命周期注释:

fn example<'a, 'b> (item1 &'a i32, item2 &'b i32) { ... }

 

标红可省略,但是会在编译阶段自动添加。

单个参数的声明周期注释用于所有输出

fn example<'a> (x: &'a i32) -> &'a i32{ ... }

同上

(类方法中)&self 或 &mut self被用于所有输出

impl<'a> Im<'a>{
    fn announce_and_return_part(&self, announcement: &str) -> &str{
        println!("{}",announcement);
        self.part
    }
}

 

静态生命周期

拥有静态生命周期的变量将在程序运行阶段一直保持有效。字符串字面值默认拥有该属性。

let s : &'static str = "Static Lifetime";

由于状态不佳,这一章的学习占用了整整一下午和一晚上的时间,看的还是云里雾里。由于笔者英语水平有限,且尚为IT初学者,难免有许多误解迷惑之处,日后回顾博客的时候想必会加以修改。

另外CNBLOGS没有Rust语法高亮,代码部分可读性也惨不忍睹,仅能供个人参考。若这篇文章有幸能被你看到,还请不吝赐教。

posted @ 2020-08-03 21:05  风坞  阅读(356)  评论(0)    收藏  举报