当前位置: 首页 > news >正文

[2023.09.13]: Rust Lang,避不开的所有权问题

Rust的所有权问题,在我学Rust的时候就跳过了,因为我知道这玩意儿没有场景就不好理解。没想到场景很快就出现了。
在开发Yew应用组件的时候,涉及到了事件,闭包,自然就引出了所有权问题。
话不多说,下面让我们直接进入代码场景,去体验并了解Rust的所有权机制吧。

下面这段代码是能够正常工作的。这段代码的逻辑意图也很简单,这是一个函数式的编辑组件,在这个组件中,有一个保存按钮。当用户点击保存按钮时,触发handle_save事件,在handle_save事件中,获取input和textarea的值,并将其通过在Props上定义的on_save Callback,传递给外部组件。

#[function_component(Editor1)]
pub fn editor1(props: &Props) -> Html {let title_ref = use_node_ref();let content_ref = use_node_ref();let Props { on_save, .. } = props;let handle_save = {let title_ref = title_ref.clone();let content_ref = content_ref.clone();let on_save = on_save.clone();Callback::from(move |_| {let title: String = if let Some(title) = title_ref.cast::<HtmlInputElement>() {title.value()} else {"none".to_string()};let content: String = if let Some(content) = content_ref.cast::<HtmlTextAreaElement>() {content.value()} else {"none".to_string()};on_save.emit(EditorData { title, content })})};html! {<div class="editor"><div class="title"><input type="text" ref={title_ref}/><Button text="保存" onclick={handle_save}/></div><div class="content"><textarea value={props.data.content.clone()} ref={content_ref}/></div></div>}
}

Rust的所有权机制最开始虽然不太好理解,但是它的编译器能够在编译时把代码的问题找出来。所以,我们也没有必要抱怨。在Rust中,只要是编译通过的代码都是好代码,哈哈哈。
我们都知道,在不讲所有权机制的编程语言中,变量的使用都比较随意,它们都会被GC或者其它内存管理机制照料。因此,我的第一版代码写出来是这个样子。

#[function_component(Editor1)]
pub fn editor1(props: &Props) -> Html {let title_ref = use_node_ref();let content_ref = use_node_ref();let handle_save = Callback::from(move |_| {let title: String = if let Some(title) = title_ref.cast::<HtmlInputElement>() {title.value()} else {"none".to_string()};});...
}

在这段代码中,直接在闭包中使用了title_ref变量,虽然已经很有Rust的语言特色了,但是还是错了,编译器给出错误提示如下

error[E0382]: use of moved value: `title_ref`--> src/components/editor1.rs:45:41|
31 |     let title_ref = use_node_ref();|         --------- move occurs because `title_ref` has type `yew::NodeRef`, which does not implement the `Copy` trait
...
34 |     let handle_save = Callback::from(move |_| {|                                      -------- value moved into closure here
35 |         let title: String = if let Some(title) = title_ref.cast::<HtmlInputElement>() {|                                                  --------- variable moved due to use in closure
...
45 |                 <input type="text" ref={title_ref}/>|                                         ^^^^^^^^^ value used here after moveFor more information about this error, try `rustc --explain E0382`.

Rust的编译器给我们做了详细的解释,特别的,运行rustc --explain E0382还能看到关于所有权的更加详细的解释。
让我来逐一解读编译器提示及报错,以便让我们更好的了解Rust的所有权机制。

第一个提示

move occurs because `title_ref` has type `yew::NodeRef`, which does not implement the `Copy` trait

title_ref是一个类型为yew::NodeRef且没有实现Copy trait的变量。这里面“没有实现Copy trait”很关键。在我们之前的经历中,遇到Copy trait相关的问题,只需要调用它的.clone()方法就可以解决。如果它没有clone方法,就在它的类型定义上加上#[derive(Clone)]

第二个提示

value moved into closure here

因为有move关键字,因此在闭包中,所有的变量的所有权都会被移动,包括title_ref。问题来了,如果我们把move关键字移除掉,又会是什么提示呢?
让我们开一个小差,把代码修改成下面这个样子。

    let handle_save = Callback::from(|_| {let title: String = if let Some(title) = title_ref.cast::<HtmlInputElement>() {title.value()} else {"none".to_string()};});

编译器的提示和报错如下

error[E0373]: closure may outlive the current function, but it borrows `title_ref`, which is owned by the current function--> src/components/editor1.rs:34:38|
34 |     let handle_save = Callback::from(|_| {|                                      ^^^ may outlive borrowed value `title_ref`
35 |         let title: String = if let Some(title) = title_ref.cast::<HtmlInputElement>() {|                                                  --------- `title_ref` is borrowed here|
note: function requires argument type to outlive `'static`--> src/components/editor1.rs:34:23|
34 |       let handle_save = Callback::from(|_| {|  _______________________^
35 | |         let title: String = if let Some(title) = title_ref.cast::<HtmlInputElement>() {
36 | |             title.value()
37 | |         } else {
38 | |             "none".to_string()
39 | |         };
40 | |     });| |______^
help: to force the closure to take ownership of `title_ref` (and any other referenced variables), use the `move` keyword|
34 |     let handle_save = Callback::from(move |_| {|                                      ++++

这个提示告诉我们,闭包的生命周期可能超出了当前函数的生命周期,但是闭包中借用了title_ref变量。也就是所可能当前函数销毁了,但是闭包还存在。这个时候在闭包中借用title_ref肯定是不合乎逻辑的,因此编译器立即拒绝了这段代码,最后给出了添加move关键字的建议。所以,我们又回到之前的那个讨论。

第三个提示

variable moved due to use in closure

这个提示告诉我们,变量title_ref的所有权被移动是因为这一行代码上使用了该变量。不得不说,这个提示很精确。

第四个报错

value used here after move

这是一个报错信息,在终端上显示的是红色,但是在这里没有把红色显示出来。这个信息告诉我们,title_ref的所有权已经被移动了,不能再使用title_ref了。
这个报错对于从其它开发语言转过来的同学,可能有点匪夷所思。就好像我名下的财产,弄来弄去,最后变成不是我了,我找谁说理去哇。
但这就是Rust的“财产”管理规则。还好它的编译器比较讲道理,把错误(第四)以及这个错误是怎么形成的(第一到第三)都给你说清楚了。
因此,要在闭包中使用title_ref,我们得clone一下。
需要注意的是,如果这个对象已经实现了Copy trait,编译器会自动生成调用copy的代码,就没有必要显示的调用clone()了。但我们的大部分struct只能通过#[derive(Clone)]来实现Clone trait。而通过#[derive(Copy)]来获取Copy trait,大部分情况,由于里面的字段,例如String类型不支持Copy trait,以失败告终。
因此,我们把代码修改成下面这样,就不会报错了。

#[function_component(Editor1)]
pub fn editor1(props: &Props) -> Html {let title_ref = use_node_ref();let content_ref = use_node_ref();let title_ref1 = title_ref.clone();let handle_save = Callback::from(move |_| {let title: String = if let Some(title) = title_ref1.cast::<HtmlInputElement>() {title.value()} else {"none".to_string()};});...}
}

然后,我参考了一下Yew官方的一些例子代码,他们通过block的方式,优雅的处理了这个所有权转移问题(在我看来,至少变量名称干扰少了,但是这种写法,在其它语言中不推荐哇,因为这个是一个赤裸裸的name shadow哇)

    let handle_save = {let title_ref = title_ref.clone();Callback::from(move |_| {let title: String = if let Some(title) = title_ref.cast::<HtmlInputElement>() {title.value()} else {"none".to_string()};})};

好了,关于Rust语言的所有权机制,我暂时就讲到这里,这只是它的冰山一角,希望给大家讲清楚了。听说还有更高级的用法,关于Rc<RefCell<>>或Arc<Mutex<>>,如果后面有遇到,再来和大家分享。

相关文章:

[2023.09.13]: Rust Lang,避不开的所有权问题

Rust的所有权问题&#xff0c;在我学Rust的时候就跳过了&#xff0c;因为我知道这玩意儿没有场景就不好理解。没想到场景很快就出现了。 在开发Yew应用组件的时候&#xff0c;涉及到了事件&#xff0c;闭包&#xff0c;自然就引出了所有权问题。 话不多说&#xff0c;下面让我们…...

Redux中间件源码解析与实现

基本介绍 本文中涉及到的关键npm包的版本信息如下&#xff1a; react 的版本为18.2.0 redux的版本为4.1.2 redux-thunk版本为2.4.2 redux-promise版本为0.6.0 redux-logger版本为3.0.6 在Redux源码解析与实现&#xff08;一&#xff09;Redux源码解析与实现&#xff08;二&…...

关于rsync用不了之后

1.尝试找出rsync使用错误原因&#xff1a; 我遇见一个问题&#xff1a;rsync:read errors mapping&#xff1a;communication error on send &#xff08;70&#xff09;&#xff0c;我查了一下这个问题很大可能是网络链接导致的&#xff0c;然后我用nslookup指令查看了/train2…...

由一个多线程并发保存而引发的思考

1. 问题描述 问题描述,现A表(用户查看实验的次数)有user_id和exp_id两个字段,其中user_id加exp_id不唯一。B表有user_id,exp_id以及exp_num三个字段,其中user_id加exp_id唯一(表中未建唯一索引)。 现需要将A表的数据同步到B表,单机模式通过定时任务同步。A表的数据有…...

python-vlc

文章目录 关于 python-vlc安装使用关于 python-vlc Python vlc bindings github : https://github.com/oaubert/python-vlcPython bindings (ctypes-based) for VLC http://olivieraubert.net/vlc/python-ctypes/关于 VLC 可参考 macOS - 安装使用 VLC https://blog.csdn.net/…...

2023长城杯 web部分题目(seekingeasy_extension)

seeking 下载题目附件得到&#xff1a; <?php error_reporting(0); header("HINT:POST n range(1,10)");$image $_GET[image]; echo "这里什么也没有&#xff0c;或许吧。"; $allow range(1, 10); shuffle($allow); if (($_POST[n] $allow[0])) …...

2-1 张量数据结构

张量概念 张量是什么&#xff1f; 单个元素叫标量&#xff08;scalar&#xff09;&#xff0c;一个序列叫向量&#xff08;vector&#xff09;&#xff0c;多个序列组成的平面叫矩阵&#xff08;matrix&#xff09;&#xff0c;多个平面组成的立方体叫张量&#xff08;tensor&…...

QSqlQuery查询语句

SqlQuery 封装了在 QSqlDatabase 上执行的 SQL 查询中创建、导航和检索数据所涉及的功能。 可用于执行 DML&#xff08;数据操作语言&#xff09;语句&#xff0c;如 SELECT、INSERT、UPDATE 和 DELETE&#xff0c; 以及 DDL&#xff08;数据定义语言&#xff09;语句&#xff…...

用c语言编写出三底模型

以下是一个用C语言实现三底模型的示例代码。这个程序通过循环遍历输入的股票数据&#xff0c;判断是否出现三底形态&#xff0c;如果是&#xff0c;则输出买入信号&#xff0c;否则输出卖出信号。 c语言 #include <stdio.h> #include <stdlib.h> // 判断是否出现…...

15 Python使用MySQL

概述 在上一节&#xff0c;我们介绍了如何在Python中使用网络&#xff0c;包括&#xff1a;套接字编程、socketserver等内容。在这一节&#xff0c;我们将介绍如何在Python中使用MySQL。MySQL是最流行的关系型数据库管理系统之一&#xff0c;由瑞典MySQL AB公司开发&#xff0c…...

3、Nginx 常用的命令和配置文件

文章目录 3、nginx 常用的命令和配置文件3.1 nginx 常用的命令&#xff1a;3.2 nginx.conf 配置文件3.2.1 地址3.2.2 内容3.2.2 vim不正常退出后再次打开信息提示解决方法 3.3 第一部分&#xff1a;全局块3.4 第二部分&#xff1a;events 块3.4 第三部分&#xff1a;http 块①、…...

python经典百题之兔子出生问题

这是一个经典的 Fibonacci 数列问题&#xff0c;可以通过递归或循环来解决。 递归方法&#xff1a; 由题意可知&#xff0c;第 n 个月的兔子数等于第 n-1 个月的兔子数加上第 n-2 个月的兔子数。设 f(n) 表示第 n 个月的兔子数&#xff0c;则有&#xff1a; f(n) f(n-1) f…...

不定积分的概念和性质

目录 原函数 不定积分 不定积分的几何意义 原函数的存在定理 不定积分的性质 不定积分是微积分的一个关键部分&#xff0c;它涉及到一个函数的不定积分的计算。不定积分可以理解为求一个函数的原函数&#xff0c;也被称为反导数。原函数是一个函数&#xff0c;使得该函数的…...

远程访问服务器JupyterLab的配置方法

远程访问服务器JupyterLab的配置方法 环境及工具注意 基本步骤生成密码生成并修改配置文件*错误&#xff1a;jupyter localhost 已拒绝连接*后台运行jupyter后台关闭 其实就是在服务器运行JupyterLab&#xff0c;然后在本地浏览器访问 环境及工具 服务器&#xff1a;Ubuntu 1…...

Java native 关键字

如你在看 JDK 的源代码的时候&#xff0c;大概率会看到很多方法使用了 native 关键字。 下面是 String 对象 JDK 中的源代码&#xff0c;就带有了一个 native 关键字。 native 是干什么用的 简单来说就是 Java 的 native 方法的实现不是用 Java 实现的&#xff0c;可能在其他…...

【线性代数】沉浸式线性代数在线学习网站

地址&#xff1a;http://immersivemath.com/ila/index.html 这是全球第一本带交互式图形的线性代数教材&#xff0c;作者是 J. Strm, K. strm, and T. Akenine-Mller。 全书一共十章&#xff0c;各章节内容如下&#xff1a; 接下来我将对各章节进行简单的总结&#xff0c;另外…...

Kotlin中特性、数据类、伴生对象、顶层函数

Kotlin中的函数参数和属性声明 在 Kotlin 中&#xff0c;函数参数和属性有不同的声明方式和行为。这些特性使得 Kotlin 代码更加安全、易于理解和维护。 函数参数的只读性 fun sum(a: Int, b: Int): Int {var modifiedA aif (modifiedA > 0) {modifiedA 1}//三元表达式v…...

《PostgreSQL物化视图:创建、维护与应用》

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f405;&#x1f43e;猫头虎建议程序员必备技术栈一览表&#x1f4d6;&#xff1a; &#x1f6e0;️ 全栈技术 Full Stack: &#x1f4da…...

shell脚本之test命令

shell脚本之test命令 数值比较&#xff1a;2. 字符串比较&#xff1a;3. 文件测试&#xff1a;4. 逻辑操作&#xff1a;5. 其他测试&#xff1a; test命令在Shell脚本中用于进行条件测试和条件判断。它用于检查文件、字符串和数值的各种条件&#xff0c;并返回一个状态码&#…...

JAVA设计模式8:装饰模式,动态地将责任附加到对象上,扩展对象的功能

作者主页&#xff1a;Designer 小郑 作者简介&#xff1a;3年JAVA全栈开发经验&#xff0c;专注JAVA技术、系统定制、远程指导&#xff0c;致力于企业数字化转型&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;蓝桥云课讲师。 目录 一、什么是装饰模式二、…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

通过Wrangler CLI在worker中创建数据库和表

官方使用文档&#xff1a;Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后&#xff0c;会在本地和远程创建数据库&#xff1a; npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库&#xff1a; 现在&#xff0c;您的Cloudfla…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置&#xff0c;使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI

前一阵子在百度 AI 开发者大会上&#xff0c;看到基于小智 AI DIY 玩具的演示&#xff0c;感觉有点意思&#xff0c;想着自己也来试试。 如果只是想烧录现成的固件&#xff0c;乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外&#xff0c;还提供了基于网页版的 ESP LA…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录&#xff0c;但是由于这个树组件的节点越来越多&#xff0c;导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多&#xff0c;导致的浏览器卡顿&#xff0c;这里很明显就需要用到虚拟列表的技术&…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”

2025年#高考 将在近日拉开帷幕&#xff0c;#AI 监考一度冲上热搜。当AI深度融入高考&#xff0c;#时间同步 不再是辅助功能&#xff0c;而是决定AI监考系统成败的“生命线”。 AI亮相2025高考&#xff0c;40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕&#xff0c;江西、…...

MySQL:分区的基本使用

目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区&#xff08;Partitioning&#xff09;是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分&#xff08;分区&#xff09;可以独立存储、管理和优化&#xff0c;…...

python爬虫——气象数据爬取

一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用&#xff1a; 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests&#xff1a;发送 …...