Rust学习笔记#2:变量声明、绑定与引用

Stella981
• 阅读 922

Rust学习笔记#2:变量声明、绑定与引用

变量声明

基础知识

Rust中的变量分局部变量和全局变量两种,且必须先声明后使用,常见的声明语法为:

// 声明局部变量,使用let关键字
let var: i32 = 100;
// 声明全局变量,使用static关键字
static GLOBAL: i32 = 0; 

由于Rust非常注重内存安全,因此全局变量的使用有许多限制,我们日常使用最多的还是局部变量。与传统的C/C++语言相比,Rust的变量声明语法不同,这样设计主要有下列三种优点:

  • 语法分析更容易:Rust的局部变量声明一定是以关键字let开头,类型一定跟在:后面,语法歧义更少,语法分析器更容易编写。
  • 类型推导更方便:Rust的变量声明的一个重要特点是,要声明的变量前置,对它的类型描述后置。这是吸取了其他语言的教训后的结果,因为在变量声明语句中,最重要的是变量本身,类型只是附属的额外描述,并非必不可少的部分,类型可以由编译器自动推导获得,因此类型后置的语法更合适。
  • 支持模式解构:let不仅能声明局部变量,还具有模式结构(pattern destructure)的功能,这里暂且不表。

Rust中变量声明默认是“只读”的,如果需要让变量可写,则需要使用mut关键字,mutmutable的简写:

let x = 5;
x = 10; // x为只读变量,因此会报编译错误
let mut y = 5; // 使用mut关键字声明一个可写变量
y = 10;

Rust中变量必须被初始化后才可以使用,否则会报编译错误。因此,不被初始化的变量是没有默认值的。变量既可以在声明时初始化,也可以在使用前初始化:

let x: i32; // 声明变量x
x = 1; // 初始化变量x,不需要x是mut,因为这是初始化不是修改
println!("{}", x);

Rust中的合法标识符(包括变量名、函数名等)必须由数字、字母、下划线组成,且不能以数字开头。注意,单独的下划线是一个特殊的标识符,在编译器内部是被特殊处理的,不能作为普通变量使用,其具体用法这里暂且不表。Rust中关于变量的命名规范是蛇形命名法,使用下划线,一般用小写,即file_name

变量遮蔽

Rust中允许在同一个代码块中声明同样名字的变量,如果这样做,后面声明的变量会将前面声明的变量遮蔽(Shadowing)起来,从而前面的变量将无法访问。例如:

let x = "hello";
println!("x is {}", x);
let x = 5;
println!("x is {}", x);

上面这种形式的代码在很多编程语言中是无法编译通过的,因为变量x被重复声明,但在Rust中是可以编译通过的:前后两个x是完全不同的两个变量,内存空间不同,类型也不同,只是恰巧名字相同。

Rust这种设计有时非常实用,例如,我们需要在同一个函数内部把一个变量转换为另一个类型的变量,但又不想给它们起不同的名字。但是,我个人认为变量遮蔽可能会带来阅读代码时的歧义,但以我现在的水平尚不能仔细分析变量遮蔽这一特性的利弊权衡,或许未来能够在RFC中找到答案。

类型推导

Rust编译器的类型推导功能很强大,不仅可以从变量声明的当前语句中获取信息进行推导,而且还能通过上下文信息进行推导。类型推导和“动态类型”是两码事,Rust仍然是静态类型的,所有变量的类型都必须在编译阶段确定,类型推导只是辅助我们不需要显示写出类型而已。

类型别名

可以使用type关键字给同一个类型起个别名,例如:

type Age = u32; // u32代表无符号的32位整数类型
let x: Age = 20;
println!("x is {}", x);

静态变量

可以用static关键字声明静态变量,这也是Rust中唯一的声明全局变量的方法:

static GLOBAL: i32 = 0;

为了保证内存安全,全局变量的使用有很多限制:

  • 必须在声明时立即初始化
  • 初始化必须是编译期可确定的常量
  • 带有mut修饰的全局变量,在使用的时候必须使用unsafe关键字。

unsafe关键字用于逃过Rust编译器的检查以写一些可能存在危险的代码,当下无须深入研究。从上面第三条限制可以看出,Rust并不鼓励我们使用可变的全局变量,更希望我们把全局变量用成全局常量。注意,全局变量的声明周期是整个程序,从启动到退出。

常量

可以使用const关键字声明一个常量:

const GLOBAL: i32 = 0;

常量和静态变量最大的区别在于:编译器不一定会给常量分配内存空间,可能会在编译过程中将常量內联优化。

变量绑定

通过let关键字来创建变量,这是Rust语言从函数式语言中借鉴的语法形式。let创建的变量一般称为绑定(binding),而不是我们通常说的赋值,因为它表明了位置表达式和值表达式之间建立的一种关联关系。

Rust中的表达式可以分为位置表达式和值表达式,在其他语言中,一般称为左值和右值。

  • 位置表达式:表示内存位置的表达式,有本地变量、静态变量、解引用、数组索引和字段引用五种。通过位置表达式可以对某个数据单元的内存进行读写。
  • 值表达式:只引用了某个存储单元地址中的数据,相当于数据值,只能进行读操作。值表达式要么是字面量,要么是表达式求值过程中创建的临时值。

表达式的求值过程在不同的上下文中会有不同的结果,求值上下文也分位置上下文和值上下文。下面几种表达式属于位置上下文(不必看懂,知道就好):

  • 赋值或者复合赋值语句左侧的操作数
  • 一元引用表达式的独立操作数
  • 包含隐式借用的操作数
  • match判别式或let绑定右侧在使用ref模式匹配的时候也是位置上下文

除了上述几种情况,其余表达式都属于值上下文。一般情况下,值表达式出现在值上下文中,位置表达式出现在位置上下文中,但也存在特殊情况:

  • 值表达式不能出现在位置上下文中,否则会报错:

    // error[E0070]: invalid left-hand side of assignment "hello" = 1;

  • 当位置表达式出现在值上下文中时,该位置表达式会把所有权转移给另外一个位置表达式。

变量引用

我们刚刚看到了一个新名词,“所有权(ownership)”,所有权代表着以下意义:

  • 每个值在Rust中都有一个变量来管理它,这个变量就是这个值、这块内存的所有者。
  • 每个值在一个时间点上只有一个管理者。
  • 当变量所在的作用域结束的时候,变量以及它代表的值将会被销毁。

当位置表达式出现在值上下文中时,这种所有权转移在Rust中称为移动语义。但在日常开发中,有时候并不需要转移所有权,Rust提供了引用操作符&,可以直接获得位置表达式的内存地址,并通过该地址进行读写操作,解引用使用*操作符。下面看一个引用和解引用的示例:

let x = 32;
let y = &x;
assert_eq!(32, *y);

在上面的代码中,32这个值的所有权归变量x所有,变量y中存储的是其地址,最后通过解引用操作符*将引用y中的值取出来,以供assert_eq!宏使用,因为变量x仍旧保留它们的所有权,所以引用也被称为借用。

生命周期是Rust的核心概念,而所有权、借用等概念又和生命周期息息相关,但目前先不去深入了解它,待把基础语法掌握后,再去学习Rust的精髓,方能事半功倍。

参考文献

  • 《Rust编程之道》张汉东
  • 《深入浅出Rust》范长春
点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
2个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这