首页 > 其他分享 >《BEGINNING RUST PROGRAMMING》---读书随记(3)

《BEGINNING RUST PROGRAMMING》---读书随记(3)

时间:2022-11-26 11:35:25浏览次数:34  
标签:String self PROGRAMMING --- json let 随记 fn 函数

BEGINNING RUST PROGRAMMING

Author: Ric Messier

如果需要电子书的小伙伴,可以留下邮箱,看到了会发送的

Chapter 3 Building a Library

REFERENCES

在我们将一个多维数组传递到一个函数中时,我们返回了新的数组,替换原来的那个。这在一定程度上是因为将数据传递到一个函数中,当试图更改它,并希望数据的更改保持完整,但这一点非常复杂。因为所有权的规则,这是不可能的。您将数据传递到函数中,函数结束后内存就会被释放,因为它超出了作用域。

如果您想传递数据以供函数使用来执行必要的事情,但最后真的需要这些数据,那该怎么办?返回数据并必须创建一个新变量来存储它是不现实的,就像的游戏程序中所做的那样。因此,我们需要一种方法来将数据传递给一个函数,而不丢失它,或者不得不做一些奇怪的事情,比如创建一个新变量,在函数返回时存储数据。

Rust提供了引用,在大多数语言中,它使您能够使用一种称为指针的数据类型来发现内存位置,其中数据类型的内容是内存地址,您可以通过引用传递或通过值传递。这意味着在堆栈上放置的供被调用函数使用的值要么是可以直接使用的实际值,要么是可以从中读取以间接获得该值的内存位置。

use std::io;
fn twice(x: &u8) -> u8 {
    x * 2
}

fn readin() -> u8 {
    let mut input = String::new();
    io::stdin().read_line(&mut input);
    input = input.trim().to_string();
    let x: u8 = input.parse::<u8>().unwrap();
    x
}

fn main() {
    let mut x: u8 = 10;
    println!("{}", twice(&x));
    x = readin();
    println!("{}", twice(&x));
}

那引用的含义就是,当我们只想传递一个值而不是实际的地址——这是通常通过调用变量名/别名来引用的地址时,我们会传递一个对变量的引用。这是使用加在变量名前面的&操作符来完成的

其实上面的代码中x变量也不会发生所有权转移,因为在Rust中,数值类型,它是编译时就可以知道占用内存的大小的,所以在传入函数时,是发生Copy语义,因为编译器知道大小就可以直接在栈上分配内存,任何需要在运行时进行分配的内容都不是Copy语义

那么在函数readin()中,String就不是可以在编译时知道大小的类型了,但是我们希望在将它传入函数之后,能够继续使用它,其实就是不希望出现所有权转移到调用的函数内部,我们希望继续持有这个变量的所有权,那么下面的代码改造一下,看看String替换数值类型之后,如何解决这个问题

use std::io;

fn twice(x: &String) -> String {
    format!("{} {}", x, x)
}

fn readin() -> String {
    let mut input = String::new();
    io::stdin().read_line(&mut input);
    input = input.trim().to_string();
    input
}

fn main() {
    let mut x: String = String::from("bogus");
    println!("{:p}", x.as_ptr());

    println!("{}", twice(&x));
    x = readin();
    println!("{}", twice(&x));

    println!("{:p}", x.as_ptr());
}

上面代码中,变量x是个String,它在传入twice函数之后,又被赋值了一次,然后又传入了twice函数,它被使用了几次,但是所有权依然在main函数中

然后上面还添加了对变量x所代表的地址打印了出来,在赋值前后,是出现两个不同的地址

FIRST PASS

Rust认识到数据并不是孤立的。它通常与其他数据片段相关联。此外,Rust语言开发人员认识到,一旦您有了一个复杂的数据类型,您就需要对它进行一些操作。这意味着您可以使用特征将函数附加到结构中——这为您提供了一种直接处理数据结构内部的数据的方法。

extern crate rustc_serialize;
use rustc_serialize::json::{self,ToJson, Json};

#[derive(RustcEncodable)]
struct Dvd {
    name: String,
    year: u16,
    cast: String,
    length: u16
}

impl ToJson for Dvd {
    fn to_json(&self) -> Json {
        Json::String(
            format!("{}+{}+{}+{}i", self.name, self.year, self.cast, self.length))
    }
}

fn converttojson (advd: &Dvd) -> String {
    json::encode(advd).unwrap()
}

fn main() {
    let a = Dvd {
        name: String::from("Four Weddings and a Funeral"),
        year: 1994,
        cast: String::from("Hugh Grant"),
        length: 117,
    };
    let encoded = converttojson(&a);
    println!("{}", encoded);
}

Traits and Implementations

Rust中的trait与C++中的interface相似,因为C++中的interface是一种抽象技术,就像Rust中的trait是一种抽象技术一样。您可以定义一个trait,包括它作为函数调用的方式,而不必担心实现,这是单独处理的。

一个trait可以让你表明你期望某物的行为,而不需要担心某物是什么

你定义了trait,在本质上,你创造了契约。

pub trait Encodable {
    fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error>;
}

impl ToJson for Dvd {
    fn to_json(&self) -> Json {
        Json::String(format!("{}+{}+{}+{}i", self.name, self.year, self.cast, self.length))
    }
}

然后这个trait的函数还是个泛型函数,也就是说它适用的范围比正常的针对特定的数据类型的函数要大,这样就不必为了每个类型都编写相同的代码

在函数名后面的括号中,表示类型被传递到函数中。然后,可以使用T来代替任何数据类型。在参数中,我们表示名为x的变量具有被传递给函数的数据类型的值。此外,我们还可以指出所返回的值也有被传入的数据类型。在本质上,这意味着数据类型是一个变量——这是在写入函数时所不知道的。但是,它在编译时是已知的,这意味着该函数可以正确地构建到可执行文件中。

这个知道需要调用什么函数的过程——这意味着当调用该函数时将引用什么地址——被称为绑定。从语言和编译器的角度来看(为了我们这里的目的,因为当您真正深入研究它时,这是一个复杂的主题),有早期绑定和后期绑定。早期绑定是编译器知道所引用的内容,编译器可以将其内置到程序中。当然,这将加快程序的速度,因为地址在运行之前是已知的。如果直到运行时才能标识该地址,则称为延迟绑定。

在序列化那段代码中,我们有一个结构体,并且希望能够使用rustc_serialize模块使该结构体可序列化。这个模块不可能知道我们已经创建的结构,这意味着必须实现一些trait。这涉及到编写基于结构中的数据来实现函数的代码。在某些情况下,Rust编译器可以为我们处理这个问题

#[derive(RustcEncodable)]

这一行告诉编译器尝试实现RustcEncodabletrait。该模块包含了针对公共或内置数据类型的实现,但是编译器猜测实现应该是什么样子的,因此它将为我们完成实现,而不需要我们编写任何代码

Self-Identification

然后还有一个点,可以看到trait的函数入参中,有一个&self这样的东西。首先,它是一个引用或者说借用,也就是说这个函数想要的是这个值而不是这个值所在的地址;然后,出现新的概念self,看起来像是将自己传给自己的意思

为了解释self的概念,首先是想象我们的数据是存在某一块内存中的,但是呢,我们为这个数据类型实现的trait或者那个函数,它是在另一个地方存储的。但从本质上说,它们是一起存在的。毕竟,这个函数是数据的一个trait。这就像你可能拥有的任何特征一样——头发颜色、眼睛颜色、身高、体重等等——这是你自己的一部分。to_json()函数只是数据结构的一部分。因此,当函数引用self时,它引用的是函数期望作用的数据结构的特定实例。

SECOND PASS

完成第一部分的JSON数据的序列化之后,接下来时使用serde库来作为JSON的序列化工具

[dependencies]
serde = { version = "*", features = ["derive"] }
serde_json = "*"
serde_derive = "*"

上面的依赖声明中,第一项使用了{},那是tuple的意思,里面可以有多项,里面的features属性可以选中多个想要的特性,也可以选中多个特性,所以是[],数组的形式

接下来是这个库的使用

extern crate serde_derive;
extern crate serde;
extern crate serde_json;

use serde::{Deserialize, Serialize};
use serde_json::Result;
use std::fs::File;
use std::fs::OpenOptions;

#[derive(Serialize, Deserialize)]
struct Dvd {
    name: String,
    year: u16,
    cast: String,
    length: u16
}

fn json_from_str(raw: &str) -> Dvd {
    serde_json::from_str(raw).unwrap()
}

fn str_from_json(dvd: &Dvd) -> String {
    serde_json::to_string(dvd).unwrap()
}

fn dvds_to_file(f: &String, d: Dvd) {
    let file = OpenOptions::new().append(true).open(f).unwrap();
    serde_json::to_writer(file, &d);
}

fn dvds_from_file(f: &String) -> Dvd {
    let file = File::open(f).unwrap();
    let deserialized_json: Dvd = serde_json::from_reader(file).unwrap();
    deserialized_json
}

The Driver

fn main() {
    let rawdata = r#"
        {
        "name": "La La Land",
        "year": 2016,
        "cast": "Emma Stone, Ryan Gosling",
        "length": 128
        }"#;
    let mut d: Dvd = json_from_str(rawdata);
    let encoded = str_from_json(&d);
    println!("{}", encoded);
    let filename = String::from("file.json");
    dvds_to_file(&filename, d);
    d = dvds_from_file(&filename);
    println!("{}", str_from_json(&d));
}

第一行代码做的是从原始字符串文字中创建一个字符串实例,这意味着它是一个由UTF-8个字符组成的静态值。因此,我们用字母r来表示。

因为我们需要在原始字符串中使用双引号(“),所以我们必须添加 (#)

标签:String,self,PROGRAMMING,---,json,let,随记,fn,函数
From: https://www.cnblogs.com/huangwenhao1024/p/16927118.html

相关文章