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

变量声明

基础知识

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

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

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

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

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

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

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

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

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

变量遮蔽

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

1let x = "hello"; 2println!("x is {}", x); 3let x = 5; 4println!("x is {}", x);

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

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

类型推导

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

类型别名

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

1type Age = u32; // u32代表无符号的32位整数类型 2let x: Age = 20; 3println!("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提供了引用操作符&,可以直接获得位置表达式的内存地址,并通过该地址进行读写操作,解引用使用*操作符。下面看一个引用和解引用的示例:

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

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

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

参考文献

  • 《Rust编程之道》张汉东
  • 《深入浅出Rust》范长春
点赞
收藏

评论区

加载中...

相关推荐

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

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )