如何在Rust中重用struct示例

8e2ybdfx  于 2023-05-07  发布在  其他
关注(0)|答案(2)|浏览(154)

我对Rust非常陌生,来自python编程语言,我构建了以下逻辑:

main.rs

use library_system::Library;
use library_system::Book;

fn main() {
    let library = Library {
        name: String::from("Malcom X")
    };

    let book = Book {
        name: String::from("The Edge of AI"),
        author: String::from("Clement Adams"),
        released_date: String::from("06/01/2021"),
        library,
    };

    println!("library created: {:?}", library);
    println!("booked created {:?}", book);
}

lib.rs

#[derive(Debug)]
pub enum Status {
    Free,
    Booked,
}

#[derive(Debug)]
pub struct Library {
    pub name: String,
}
#[derive(Debug)]
pub struct Book {
    pub name: String,
    pub author: String,
    pub released_date: String,
    pub library: Library,
}

由于某种原因,当我试图在终端中打印上面代码的结果时,我在编译时收到了这样的消息:

error[E0382]: borrow of moved value: `library`
  --> src/main.rs:19:39
   |
6  |     let library = Library {
   |         ------- move occurs because `library` has type `Library`, which does not implement the `Copy` trait
...
16 |         library,
   |         ------- value moved here
...
19 |     println!("library created: {:?}", library);
   |                                       ^^^^^^^ value borrowed here after move

解决这个问题的适当方法是什么?
我试着解决这个问题:

let library_clone = library.clone();

println!("library created: {:?}", library_clone);

但我现在面对的问题是:

error[E0599]: no method named `clone` found for struct `Library` in the current scope
  --> src/main.rs:10:33
   |
10 |     let library_clone = library.clone();
   |                                 ^^^^^ method not found in `Library`
hujrc8aj

hujrc8aj1#

Rust采用单一所有权模型,这意味着每一个值在代码中的一个位置上都是唯一的。默认情况下,当你传递一个参数给一个函数时,你传递它 * 通过值 *,这意味着你传递完全的所有权给这个函数。一个必然的结果是,如果你将所有权传递给一个函数,你就不再有权对这个值做任何事情。(实现Copy trait的类型,例如数值类型,是例外,但您的类型不符合Copy
如果你想让一个函数查看你的数据,而不给它完全的所有权,你应该传递一个对函数的引用。传递一个引用允许函数借用你的值而不完全控制它。

println!("library created: {:?}", &library);

注意library之前的&
此外,由于您提到了clone,您可能应该为您的结构实现它。你可以用手来做

impl Clone for Library {
  fn clone(&self) -> Library {
    Library { name: self.name.clone() }
  }
}

但是“通过克隆self的所有部分来克隆self”的模式是如此普遍,以至于Clone trait有一个derive宏。所以你可以只写

#[derive(Clone, Debug)]
pub struct Library {
  ...
}
h5qlskok

h5qlskok2#

在Rust中,赋值的工作方式与Python或C++不同。对于Rust中的许多类型,当您将var1分配给var2时,var1中的值被移动到var2中,然后var1未初始化,不再包含该值。此后,编译器将不允许您使用未初始化的变量。
如果您这样做:

let s1 = String::from("hello");
let s2 = s1;

println!("{}, world!", s1);

你会得到这个错误:

--> src/main.rs
  |
2 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 |     let s2 = s1;
  |              -- value moved here
4 |
5 |     println!("{}, world!", s1);
  |                            ^^ value borrowed here after move
  |

根据The Rust Programming Language,该错误的原因是因为s1是一个指针,指向存储String的内存中的一个点,当您写入时:

let s2 = s1;

Rust将指针复制到s2中。这使得你有两个指针指向内存中的同一位置,当s1s2最终超出范围时,Rust将释放s1s2指向的内存,这将导致相同的内存被释放两次,这被称为“双重释放”错误。
为了防止相同的内存被释放两次,Rust在将s1指针分配给s2后立即使其无效。这就是所谓的“一步棋”:指针已经从一个变量移动到另一个变量。

***借用***是指创建对另一个变量的引用,即指向另一个变量的指针,即不是变量中值的副本,例如:&library,这通常不是错误。

您的代码抛出了与上面的String示例相同的错误,因此看起来类似的事情正在发生。然而,我搜索了一下,被赋值为Struct的变量并不包含指向内存中某个位置的指针,所以不存在“双自由”问题。然而,下面的代码抛出了相同的“borrow after move”错误:

#[derive(Debug)]
struct Library {
    age: u16,
}

fn main() {
    let s1 = Library {age: 31};
    let _s2 = s1;

    println!("{:?}", s1);
}

错误:

error[E0382]: borrow of moved value: `s1`
  --> src/main.rs:10:22
   |
7  |     let s1 = Library {age: 31};
   |         -- move occurs because `s1` has type `Library`, which does not implement the `Copy` trait
8  |     let _s2 = s1;
   |               -- value moved here
9  |
10 |     println!("{:?}", s1);
   |                      ^^ value borrowed here after move
   |

错误是说分配给s1变量的值已经移动到s2变量中,此后println!()试图使用s1值(但它不再存在了!).
据我所知,规则似乎是:如果一个值没有实现Copy trait,那么当你将一个包含该值的变量赋值给另一个变量时,Rust会移动该值并使第一个变量无效。
在做了更多的研究之后....“Programming Rust(2nd Edition)”第85页,简单地说:
在Rust中,对于大多数类型,像将值分配给变量,将其传递给函数或从函数返回这样的操作不会复制值:他们移动它。线人放弃。。值到目的地,[源]变为未初始化...
你可能会感到惊讶,Rust会改变这些基本操作的含义;当然,在历史上,分配是一件应该被很好地确定下来事情。然而,如果你仔细观察不同的语言是如何选择处理作业的,你会发现不同学校之间实际上有很大的差异。
这本书解释了赋值如何在Python中使用以下代码:

s = ["udon", "ramen", "soba"]
t = s
u = s

在赋值t = su = s中,Python将s中的指针复制到tu中,并将列表的引用计数增加到3。赋值是非常有效的,因为列表不会被复制。另一方面,Python必须保持列表的引用计数,并且只有当引用计数下降到0时,即当stu都退出作用域时,内存是否被释放。引用计数和随之而来的垃圾收集会产生开销,即会减慢执行速度。
这本书将Python的赋值与C进行了比较。以下是本书中使用的C示例:

using namespace std;

vector<string> s = {"udon", "ramen", "soba"};
vector<string> t = s;
vector<string> u = s;

这本书说C++的作业:

vector<string> t = s;
vector<string> u = s;

复制向量,内存中最终有三个向量,总共包含九个字符串。不需要引用计数和垃圾收集:当变量超出作用域时,包含其副本的内存将被释放。但是,复制值(尤其是大型复杂值)需要更多的时间和内存。
这本书解释了一个类似的Rust示例:

let s = vec!["udon".to_string(), "ramen".to_string(), "soba".to_string()];
let t = s;
let u = s;

初始化:

let t = s;

将向量从s移动到t。向量的元素保持在内存中的位置。没有需要调整的引用计数,当t超出范围时,将释放向量占用的内存。而且,编译器现在认为s未初始化。
那么当我们到达初始化时会发生什么:

let u = s;

这会将未初始化的值s分配给u。Rust谨慎地禁止使用未初始化的值,因此编译器会拒绝此代码,并返回以下错误:
[你得到了类似的移动错误]
回到你的程序。您将library变量分配给book Struct中的一个字段,因此Rust将library变量中的值移动到book Struct中的字段中,并立即取消初始化library变量。此后,编译器将不允许您使用未初始化的library变量:你会得到一个错误

对不起,这个变量中曾经有一个值,但是你把这个值移到了另一个变量中,现在这个变量没有初始化。然后,您随后尝试使用此变量。别这样!
你认为这段代码会发生什么:

fn do_stuff(str: String) {
    println!("{} world", str);
}

fn main() {
    let s = String::from("hello");  
    do_stuff(s); 
    
    println!("{}", s);  //<==** What happens here?
}

这对于任何来自Python或C++的人来说都应该是非常令人惊讶的!
现在,可以更好地理解***借款。看看这个例子:

let s = vec!["udon".to_string(), "ramen".to_string(), "soba".to_string()];
let t = &s;
let u = &s;

println!("{}", s[0]);
println!("{}", t[1]);
println!("{}", u[2]);

输出:

udon
ramen
soba

当你创建一个变量的引用时,例如&s,并将引用分配给另一个变量,则不会移动任何内容。此外,当tu超出作用域时,不会释放包含向量的内存:

let s = vec!["udon".to_string(), "ramen".to_string(), "soba".to_string()];

{
    let t = &s;
    let u = &s;

    println!("{}", s[0]);
    println!("{}", t[1]);
    println!("{}", u[2]);
}

println!("{}", s[0]);

输出:

udon
ramen
soba
udon

只有当s超出作用域时,包含向量的内存才会被释放。

相关问题