Pratham Patel 发布的文章

上一篇文章 中,你学习了函数。在这篇文章中,我们将学习使用条件语句来管理 Rust 程序的控制流。

什么是条件语句?

在编写代码的时候,最常见的任务之一就是检查某些条件是否为 truefalse。“如果温度高于 35°C,打开空调。”

通过使用 ifelse 这样的关键字(有时候结合使用),程序员可以根据条件(例如提供的参数数量、从命令行传递的选项、文件名、错误发生等)改变程序的行为。

所以,对一个程序员来说,了解任何语言的控制流都是至关重要的,更不用说 Rust 了。

条件运算符

下表列出了所有常用的单个条件运算符:

运算符示例解释
>a > ba 大于 b
<a < ba 小于 b
==a == ba 等于 b
!=a != ba 不等于 b
>=a >= ba 大于等于 b
<=a <= ba 小于等于 b

以及下表是逻辑运算符,它们用于一个或多个条件之间:

运算符示例解释
` ` (逻辑或)`条件1 条件2`条件1条件2 中至少有一个为 true
&& (逻辑与)条件1 && 条件2所有 条件都为 true
! (逻辑非)!条件条件 的布尔值的相反值
? 与数学相似,你可以使用圆括号来指定操作的优先级。

使用 if else

要控制 Rust 代码的基本流程,使用两个关键字:ifelse。这可以根据提供的条件的状态创建两个“执行路径”。

一个简单的带有替代执行路径的 if 块的语法如下:

if 条件 {
    <语句>;
} else {
    <语句>;
}
? 当只有一个条件时,将其括在圆括号中并不是强制性的。根据语法,使用圆括号是可选的。你仍然应该使用它们来指定优先级并优化可读性。

来看看一个例子。

fn main() {
    let a = 36;
    let b = 25;

    if a > b {
        println!("a 大于 b");
    } else {
        println!("b 大于 a");
    }
}

这里,我声明了两个整数变量 ab,它们的值分别为 '36' 和 '25'。在第 5 行,我检查变量 a 中存储的值是否大于变量 b 中存储的值。如果条件计算结果为 true,则会执行第 6 行的代码。如果条件计算结果为 false,由于我们有一个 else 块(可选),第 8 行的代码将被执行。

来看看程序的输出。

a 大于 b

完美!

来修改一下变量 a 的值,使其小于变量 b 的值,看看会发生什么。我将把 a 的值改为 '10'。修改后的输出如下:

b 大于 a

但是,如果我将相同的值存储在变量 ab 中呢?为了看到这一点,我将两个变量的值都设置为 '40'。修改后的输出如下:

b 大于 a

嗯?从逻辑上讲,这没有任何意义... :frowning:

但是这可以改进!我们继续。

使用 else if 条件

与其他任何编程语言一样,你可以使用 else if 块来提供多于两个的执行路径。语法如下:

if 条件 {
    <语句>;
} else if 条件 {
    <语句>;
} else {
    <语句>;
}

现在,通过使用 else if 块,我可以改进程序的逻辑。下面是修改后的程序。

fn main() {
    let a = 40;
    let b = 40;

    if a == b {
        println!("a 与 b 是相等的");
    } else if a > b {
        println!("a 大于 b");
    } else {
        println!("b 大于 a");
    }
}

现在,我的程序的逻辑是正确的。它已经处理了所有的边缘情况(我能想到的)。第 5 行处理了 a 等于 b 的情况。第 7 行处理了 a 可能大于 b 的情况。而 a 小于 b 的情况则由第 9 行的 else 块隐式处理。

现在,当我运行这段代码时,我得到了以下输出:

a 与 b 是相等的

现在这就完美了!

示例:找到最大值

我知道使用 ifelse 很容易,但是让我们再看一个程序。这次,我们来比较三个数字。我还将在这个实例中使用逻辑运算符!

fn main() {
    let a = 73;
    let b = 56;
    let c = 15;

    if (a != b) && (a != c) && (b != c) {
        if (a > b) && (a > c) {
            println!("a 是最大的");
        } else if (b > a) && (b > c) {
            println!("b 是最大的");
        } else {
            println!("c 是最大的");
        }
    }
}

这个程序第一眼看上去可能很复杂,但是不要害怕,我会解释的!

最开始,我声明了三个变量 abc,并赋予了我能想到的随机值。然后,在第 6 行,我检查了没有变量的值与其他变量相同的条件。首先,我检查 ab 的值,然后是 ac,最后是 bc。这样我就可以确定没有变量中存储了重复的值。

然后,在第 7 行,我检查了变量 a 中存储的值是否是最大的。如果这个条件计算结果为 true,则会执行第 8 行的代码。否则,将检查第 9 行的执行路径。

在第 9 行,我检查了变量 b 中存储的值是否是最大的。如果这个条件计算结果为 true,则会执行第 10 行的代码。如果这个条件也是 false,那么只有一种可能。3 个变量中的最大值既不是 a 也不是 b

所以,自然地,在 else 块中,我打印出变量 c 拥有最大值。

来看看程序的输出:

a 是最大的

这是预期的结果。尝试修改分配给每个变量的值,并自己测试一下! :smiley:

总结

你学习到了如何使用 ifelse 语句。在你继续使用大量 if else if 语句制作自己的 AI 之前(哈哈),让我们在本系列的下一篇文章中学习 Rust 中的循环。

持续关注。

(题图:MJ/3eea3bbb-b630-4470-ae21-391ab86cd5bf)


via: https://itsfoss.com/rust-if-else/

作者:Pratham Patel 选题:lkxed 译者:Cubik65536 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在这一章中,在实例的帮助下,学习如何使用函数并从中返回值。

就跟任何现代编程语言一样,Rust 也有函数。

你已经熟悉的函数是 main 函数。这个函数在程序启动时被调用。

但是其他函数呢?在本文中,你将学习如何在 Rust 程序中使用函数。

函数的基本语法

你可能已经在我们声明 main 函数时知道了这一点,不管怎么样,还是让我们看一下声明函数的语法。

// 声明函数
fn function_name() {
    <statement(s)>;
}

// 调用函数
function_name();

来让我们看一个简单的函数,它将字符串 "Hi there!" 打印到标准输出。

fn main() {
    greet();
}

fn greet() {
    println!("Hi there!");
}
? 与 C 不一样的是,不管你是否要在声明或定义之前调用函数都没有关系。只要这个函数在 某个地方 被声明了,Rust 就会处理它。

正如预期,它的输出如下:

Hi there!

这挺简单的。让我们把它提升到下一个级别。让我们创建一个接受参数并返回值的函数。有没有参数和有没有返回值这两者之间无关。

使用函数接受参数

声明一个接受参数的函数的语法如下:

// 声明函数
fn function_name(variable_name: type) {
    <statement(s)>;
}

// 调用函数
function_name(value);

你可以把函数参数想象成一个传递给函数的 元组。它可以接受多种数据类型的参数,而且你可以接受任意多个参数。所以,你不必局限于接受相同类型的参数。

与某些语言不同的是,Rust 没有 默认参数在调用函数时填充所有参数是强制性的

示例:饥饿函数

来让我们看一个程序来更好地理解这个。

fn main() {
    food(2, 4);
}

fn food(theplas: i32, rotis: i32) {
    println!(
        "我饿了... 我需要 {} 个葫芦巴叶饼和 {} 个罗提!",
        theplas, rotis
    );
}

在第 5 行,我声明了一个名为 food 的函数。这个函数接受 2 个参数:theplasrotis(印度食物的名字)。然后我打印了这些变量的内容。

对于 main 函数,我使用参数 24 调用 food 函数。这意味着 theplas 被赋值为 2rotis 被赋值为 4

来让我们看一下程序的输出:

我饿了... 我需要 2 个葫芦巴叶饼和 4 个罗提!

我现在真的饿了... ?

从函数返回值

就像函数可以接受参数一样,函数也可以返回一个或多个值。这样的函数的语法如下:

// 声明函数
fn function_name() -> data_type {
    <statement(s)>;
}

// 调用函数
let x = function_name();

函数可以使用 return 关键字或者使用表达式而不是语句来返回一个值。

等等!什么是表达式?

在进一步之前:语句与表达式

在讲解 Rust 函数的例子中提起这个可能不太合适,但是你应该理解 Rust 和其他编程语言中语句和表达式的区别。

语句是以分号结尾且 不会计算出某个值 的代码行。另一方面,表达式是一行不以分号结尾且计算出某个值的代码行。

来让我们用一个例子来理解:

fn main() {
    let a = 873;
    let b = {
        // 语句
        println!("Assigning some value to b...");

        // 表达式
        a * 10
    };

    println!("b: {b}");
}

在第 3 行,我开始了一个代码块,在这个代码块中我有一个语句和一个表达式。注释标明了哪个是哪个。

在第 5 行的代码不会计算出某个值,因此需要以分号结尾。这是一个语句。

第 8 行的代码计算出了一个值。它是 a * 10873 * 10),并计算出了 8730。因为这一行没有以分号结尾,所以这是一个表达式。

? 使用表达式是从代码块中返回某些东西的一种方便的方法。因此,当返回一个值时,它是 return 关键字的替代方案。表达式不仅仅用于从函数中 “返回” 一个值。正如你刚刚看到的,a * 10 的值是从内部作用域 “返回” 到外部作用域,并赋值给变量 b。一个简单的作用域不是一个函数,但表达式的值仍然被 “返回” 了。

示例:购买腐烂的水果

来让我们看一个演示以理解函数如何返回一个值:

fn main() {
    println!(
        "如果我从水果摊买了 2 公斤苹果,我必须付给他们 {} 印度卢比。",
        retail_price(2.0)
    );
    println!(
        "但是,如果我从水果摊买了 30 公斤苹果,我就要付给他们 {} 印度卢比。",
        wholesale_price(30.0)
    );
}

fn retail_price(weight: f64) -> f64 {
    return weight * 500.0;
}

fn wholesale_price(weight: f64) -> f64 {
    weight * 400.0
}

我在上述代码中有两个函数:retail_pricewholesale_price。两个函数都接受一个参数并将值存储在 weight 变量中。这个变量的类型是 f64,函数签名表示最终函数返回一个 f64 值。

这两个函数都将购买的苹果的重量乘以一个数字。这个数字表示苹果的当前每公斤价格。由于批发商有大量订单,物流在某种程度上更容易,价格可以降低一点。

除了每公斤价格之外,这两个函数还有一个区别。那就是,retail_price 函数使用 return 关键字返回乘积。而 wholesale_price 函数使用表达式返回乘积。

如果我从水果摊买了 2 公斤苹果,我必须付给他们 1000 印度卢比。
但是,如果我从水果摊买了 30 公斤苹果,我就要付给他们 12000 印度卢比。

输出显示,从函数返回值的两种方法都按预期工作。

返回多个值

你可以有一个返回不同类型的多个值的函数。你有很多选择,但返回一个元组是最简单的。

接下来是一个示例:

fn main() {
    let (maths, english, science, sanskrit) = tuple_func();

    println!("数学考试得分: {maths}");
    println!("英语考试得分: {english}");
    println!("科学考试得分: {science}");
    println!("梵语考试得分: {sanskrit}");
}

fn tuple_func() -> (f64, f64, f64, f64) {
    // return marks for a student
    let maths = 84.50;
    let english = 85.00;
    let science = 75.00;
    let sanskrit = 67.25;

    (maths, english, science, sanskrit)
}

函数 tuple_func 返回 4 个封装在一个元组中的 f64 值。这些值是一个学生在四门科目(满分 100 分)中获得的分数。

当函数被调用时,这个元组被返回。我可以使用 tuple_name.0 方案打印这些值,但我认为最好先解构元组,这样可以帮助我们搞清楚值对应的是什么。然后我使用了包含被解构的元组的值的变量来打印分数。

这是我得到的输出:

数学考试得分: 84.5
英语考试得分: 85
科学考试得分: 75
梵语考试得分: 67.25

总结

本文介绍了 Rust 编程语言中的函数。这些是函数的 “类型”:

  • 不接受任何参数也不返回任何值的函数
  • 接收一个或多个参数的函数
  • 给调用者返回一个或多个值的函数

你知道接下来是什么吗?Rust 中的条件语句,也就是 if-else。请继续关注并享受学习 Rust 的过程。

(题图:MJ/5a07503b-c691-4276-83b2-bb42f5fda347)


via: https://itsfoss.com/rust-functions/

作者:Pratham Patel 选题:lkxed 译者:Cubik65536 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在 Rust 系列的第四篇中,学习复合数据类型、数组和元组。

在上一篇文章中,你学习到了 Rust 中的 标量数据类型。它们是整型、浮点数、字符和布尔值。

在本文中,我们将会看看 Rust 编程语言中的复合数据类型。

Rust 中的复合数据类型是什么?

复合数据类型可以在一个变量中存储多个值。这些值可以是相同的标量数据类型,也可以是不同的标量数据类型。

Rust 编程语言中有两种这样的数据类型:

  • 数组 Array :存储相同类型的多个值。
  • 元组 Tuple :存储多个值,可以是相同的类型,也可以是不同的类型。

让我们了解一下它们吧!

Rust 中的数组

Rust 编程语言中的数组具有以下特性:

  • 每一个元素都必须是相同的类型
  • 数组有一个固定的长度
  • 数组存储在堆栈中,即其中存储的数据可以被 迅速 访问

创建数组的语法如下:

// 无类型声明
let variable_name = [element1, element2, ..., elementn];

// 有类型声明
let variable_name: [data_type; array_length] = [element1, element2, ..., elementn];

数组中的元素是在方括号中声明的。要访问数组的元素,需要在方括号中指定要访问的索引。

来让我们看一个例子来更好地理解这个。

fn main() {
    // 无类型声明
    let greeting = ['H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!'];

    // 有类型声明
    let pi: [i32; 10] = [1, 4, 1, 5, 9, 2, 6, 5, 3, 5];

    for character in greeting {
        print!("{}", character);
    }

    println!("\nPi: 3.1{}{}{}{}", pi[0], pi[1], pi[2], pi[3]);
}

这里,我定义了一个字符数组和另一个存储 i32 类型的值的数组。greeting 数组以单独字符的形式存储了字符串 "Hello world!" 的字符。pi 数组以单独数字的形式存储了圆周率小数点后的前 10 位数字。

然后,我使用 for 循环打印了 greeting 数组的每个字符。(我很快就会讲到循环。)然后,我打印了 pi 数组的前 4 个值。

Hello world!
Pi: 3.11415

如果你想创建一个数组,其中每个元素都是 y,并且出现 x 次,你可以使用以下快捷方式在 Rust 中实现:

let variable_name = [y; x];

来看一个演示……

fn main() {
    let a = [10; 5];

    for i in a {
        print!("{i} ");
    }
    println!("");
}

我创建了一个变量 a,它的长度为 5。数组中的每个元素都是 '10'。我通过使用 for 循环打印数组的每个元素来验证这一点。

它的输出如下:

10 10 10 10 10
? 作为练习,尝试创建一个长度为 x 的数组,然后尝试访问数组的第 x+1 个元素。看看会发生什么。

Rust 中的元组

Rust 中的元组具有以下特性:

  • 就像数组一样,元组的长度是固定的
  • 元素可以是相同的/不同的标量数据类型
  • 元组存储在堆栈中,所以访问速度更快

创建元组的语法如下:

// 无类型声明
let variable_name = (element1, element2, ..., element3);

// 有类型声明
let variable_name: (data_type, ..., data_type) = (element1, element2, ..., element3);

元组的元素写在圆括号中。要访问元素,使用点运算符,后跟该元素的索引。

fn main() {
    let a = (38, 923.329, true);
    let b: (char, i32, f64, bool) = ('r', 43, 3.14, false);

    println!("a.0: {}, a.1: {}, a.2: {}", a.0, a.1, a.2);
    println!("b.0: {}, b.1: {}, b.2: {}, b.3: {}", b.0, b.1, b.2, b.3);

    // 元组解构
    let pixel = (50, 0, 200);
    let (red, green, blue) = pixel;
    println!("red: {}, green: {}, blue: {}", red, green, blue);
}

在上面的代码中,我在第 2 行和第 3 行声明了两个元组。它们只包含我当时想到的随机值。但是仔细看,两个元组中每个元素的数据类型都不同。然后,在第 5 行和第 6 行,我打印了两个元组的每个元素。

在第 9 行,我声明了一个名为 pixel 的元组,它有 3 个元素。每个元素都是组成像素的颜色红色、绿色和蓝色的亮度值。这个范围是从 0 到 255。所以,理想情况下,我会声明类型为 (u8, u8, u8),但是在学习代码时不需要这样优化 ; )

然后,在第 10 行,我“解构”了 pixel 元组的每个值,并将其存储在单独的变量 redgreenblue 中。然后,我打印了 redgreenblue 变量的值,而不是 pixel 元组的值。

让我们看看输出……

a.0: 38, a.1: 923.329, a.2: true
b.0: r, b.1: 43, b.2: 3.14, b.3: false
red: 50, green: 0, blue: 200

看起来不错 : )

额外内容:切片

准确的来说, 切片 Slice 不是 Rust 中的复合数据类型。相反,切片是现有复合数据类型的 “切片”。

一个切片由三个元素组成:

  • 一个初始索引
  • 切片运算符(....=
  • 一个结束索引

接下来是数组切片的一个示例:

fn main() {
    let my_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    let my_slice = &my_array[0..4];

    for element in my_slice {
        println!("{element}");
    }
}

就像 C 和 C++ 一样,& 用于存储变量的引用(而不是原始指针)。所以 &my_array 意味着对变量 my_array 的引用。

然后,来看看切片。切片由 [0..4] 表示。这里,0 是切片开始的索引。而 4 是切片结束的索引。这里的 4 是一个非包含索引。

这是程序输出,以更好地理解正在发生的事情:

0
1
2
3

如果你想要一个 包含 范围,你可以使用 ..= 作为包含范围的切片运算符。

fn main() {
    let my_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    let my_slice = &my_array[0..=4];

    for element in my_slice {
        println!("{element}");
    }
}

现在,这个范围是从第 0 个元素到第 4 个元素,下面是输出来证明这一点:

0
1
2
3
4

总结

本文讲到了 Rust 编程语言中的复合数据类型。你学习了如何声明和访问存储在数组和元组类型中的值。此外,你还了解了切片“类型”,以及如何解构元组。

在下一章中,你将学习如何在 Rust 程序中使用函数。敬请关注。

(题图:MJ/22a0d143-2216-439f-8e1d-abd94cdfdbd0)


via: https://itsfoss.com/rust-arrays-tuples/

作者:Pratham Patel 选题:lkxed 译者:Cubik65536 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

上一篇 关于 Rust 编程语言的文章中,我们提到了变量、常量和 遮蔽 shadowing

现在来讲解数据类型是再自然不过的了。

数据类型是什么?

将这个词汇展开些单词的顺序改变一下你就会得到答案了;“数据类型” -> “数据的类型”。

计算机使用 01 来存储数据,但是为了让数据在读取时有意义,我们使用数据类型来表示这些 01 的含义。

Rust 有两种数据类型:

  • 标量数据类型:只能存储单个值的类型。
  • 复合数据类型:可以存储多个值,甚至是不同类型的值。

在本文中,我将讲解标量数据类型。我将在下一篇文章中讲解第二类数据类型。

接下来是 Rust 中四种主要标量数据类型的简要概述:

  • 整型:存储整数。有每种特定情况下使用的子类型。
  • 浮点数:存储带有小数部分的数字。有两种基于大小的子类型。
  • 字符:使用 UTF-8 编码存储单个字符。(是的,你可以在字符中存储表情符号*。)
  • 布尔值: 存储 truefalse。(给那些无法就 0true 还是 0false 达成一致的开发者。)

整型

在编程语言中,整型指的是一个整数。Rust 中的整型要么是有符号的,要么是无符号的。无符号整型只能存储 0 和正数,而有符号整型可以存储负数、0 和正数。

? 一个有符号整型的范围从 -(2<sup> n-1</sup>) 开始,以 (2<sup> n-1</sup>)-1 结束。同样,无符号整型的范围从 0 开始,以 (2<sup> n</sup>)-1 结束。

这是根据符号和长度可用的整型:

Rust 中的整型数据类型

正如你所见,Rust 有 8、16、32、64 甚至 128 位的有符号和无符号整型!

使用 *size 的整型根据计算机的架构而变化。在 8 位微控制器上,它是 *8,在 32 位的旧计算机上,它是 *32,在现代 64 位系统上,它是 *64

使用 *size 是为了存储与内存(这与裸机相关)有关的数据,比如指针、偏移量等。

? 当你没有显式地指定整型的子类型时,Rust 编译器会默认推断为 i32。显然,如果值比 i32 能存储的值大或小,Rust 编译器会礼貌地报错并要求你手动指定类型。

Rust 不仅允许你以十进制形式存储整数,还允许你以二进制、八进制和十六进制形式存储整数。

为了更好的可读性,你可以使用下划线 _ 来代替逗号来书写/读取大数。

fn main() {
    let bin_value = 0b100_0101; // 使用前缀“0b”表示二进制
    let oct_value = 0o105; // 使用前缀“0o”表示八进制
    let hex_value = 0x45; // 使用前缀“0x”表示十六进制
    let dec_value = 1_00_00_000; // 和写一克若(1,00,00,000)一样

    println!("二进制值: {bin_value}");
    println!("八进制值: {oct_value}");
    println!("十六进制值: {hex_value}");
    println!("十进制值: {dec_value}");
}

我使用二进制、八进制和十六进制分别将十进制数 69 存储在变量 bin_valueoct_valuehex_value 中。在变量 dec_value 中,我存储了数字 1 克若 1 Crore (一千万),并且使用了下划线替代逗号,这是印度的书写系统。对于那些更熟悉国际计数系统的人来说,你可以将其写成 10_000_000

在编译并运行这个二进制文件后,我得到了如下输出:

二进制值: 69
八进制值: 69
十六进制值: 69
十进制值: 10000000

浮点数

浮点数是一种存储带有小数部分的数字的数据类型。

与 Rust 中的整型不同,浮点数只有两种子类型:

  • f32: 单精度浮点数类型
  • f64: 双精度浮点数类型

和 Rust 中的整型一样,当 Rust 推断一个变量的类型时,如果它看起来像一个浮点数,那么它就会被赋予 f64 类型。这是因为 f64 类型比 f32 类型有更高的精度,并且在大多数计算操作中几乎和 f32 类型一样快。请注意,浮点数据类型(f32f64)都是有符号

? Rust 编程语言按照 IEEE 754 二进制浮点数表示与算术标准存储浮点数。
fn main() {
    let pi: f32 = 3.1400; // f32
    let golden_ratio = 1.610000; // f64
    let five = 5.00; // 小数点表示它必须被推断为浮点数
    let six: f64 = 6.; // 尽管类型说明被显式的添加了,小数点也是**必须**的

    println!("pi: {pi}");
    println!("黄金比例: {golden_ratio}");
    println!("五: {five}");
    println!("六: {six}");
}

仔细看第 5 行。尽管我已经为变量 six 指定了类型,但我必须至少加上一个小数点。小数点之后有什么就由你决定了。

程序的输出是相当可预测的... 吗?

pi: 3.14
黄金比例: 1.61
五: 5
六: 6

在上面的输出中,你可能已经注意到,当显示变量 pigolden_ratiofive 中存储的值时,我在变量声明时在结尾增加的零已经消失了。

就算这些零没有被 移除,它们也会在通过 println 宏输出值时被省略。所以,不,Rust 没有篡改你的变量值。

字符

你可以在一个变量中存储一个字符,类型是 char。像 80 年代的传统编程语言一样,你可以存储一个 ASCII 字符。但是 Rust 还扩展了字符类型,以存储一个有效的 UTF-8 字符。这意味着你可以在一个字符中存储一个表情符号 ?

? 一些表情符号实际上是两个已有表情符号的组合。一个很好的例子是“燃烧的心”表情符号:❤️‍?。这个表情符号是通过使用 零宽度连接器 来组合两个表情符号构成的:❤️ + ? = ❤️‍?

Rust 的字符类型无法存储这样的表情符号。

fn main() {
    let a = 'a';
    let p: char = 'p'; // 带有显性类型说明
    let crab = '?';

    println!("Oh look, {} {}! :{}", a, crab, p);
}

正如你所见,我已经将 ASCII 字符 'a' 和 'p' 存储在变量 ap 中。我还在变量 crab 中存储了一个有效的 UTF-8 字符,即螃蟹表情符号。然后我打印了存储在每个变量中的字符。

这是输出:

Oh look, a ?! :p

布尔值

在 Rust 中,布尔值类型只存储两个可能的值之一:truefalse。如果你想显性指定类型,请使用 bool

fn main() {
    let val_t: bool = true;
    let val_f = false;

    println!("val_t: {val_t}");
    println!("val_f: {val_f}");
}

编译并执行上述代码后,结果如下:

val_t: true
val_f: false

额外内容:显性类型转换

在上一篇讲述 Rust 编程语言中的变量的文章中,我展示了一个非常基础的 温度转换程序。在那里,我提到 Rust 不允许隐式类型转换。

但这不代表 Rust 也不允许 显性 类型转换 ; )

要进行显性类型转换,使用 as 关键字,后面跟着要转换的数据类型。

这是一个示例程序:

fn main() {
    let a = 3 as f64; // f64
    let b = 3.14159265359 as i32; // i32

    println!("a: {a}");
    println!("b: {b}");
}

在第二行,我没有使用 3.0,而是在 3 后面写上 as f64,以表示我希望编译器将 3(一个整数)转换为 64 位浮点数的类型转换。第三行也是一样。但是这里,类型转换是有损的。这意味着小数部分 完全消失。它不是存储为 3.14159265359,而是存储为简单的 3

程序的输出可以验证这一点:

a: 3
b: 3

总结

本文介绍了 Rust 中的原始/标量数据类型。主要有四种这样的数据类型:整型、浮点数、字符和布尔值。

整型用于存储整数,它们有几种子类型,基于它们是有符号还是无符号以及长度。浮点数用于存储带有小数的数字,根据长度有两种子类型。字符数据类型用于存储单个有效的 UTF-8 编码字符。最后,布尔值用于存储 truefalse 值。

在下一章中,我将讨论数组和元组等复合数据类型。敬请关注。

(题图:MJ/c0c49e15-cc9d-4eef-8e52-2f0d62294965)


via: https://itsfoss.com/rust-data-types/

作者:Pratham Patel 选题:lkxed 译者:Cubik65536 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

推进你的 Rust 学习,熟悉 Rust 程序的变量和常量。

该系列的第一章中,我讲述了为什么 Rust 是一门越来越流行的编程语言。我还展示了如何 在 Rust 中编写 Hello World 程序

让我们继续 Rust 之旅。在本文中,我将向你介绍 Rust 编程语言中的变量和常量。

此外,我还将讲解一个称为“ 遮蔽 shadowing ”的新编程概念。

Rust 变量的独特之处

在编程语言中,变量是指 存储某些数据的内存地址的一个别名

对 Rust 语言来讲也是如此。但是 Rust 有一个独特的“特性”。每个你声明的变量都是 默认 不可变的 immutable 。这意味着一旦给变量赋值,就不能再改变它的值。

这个决定是为了确保默认情况下,你不需要使用 自旋锁 spin lock 互斥锁 mutex 等特殊机制来引入多线程。Rust 会保证 安全的并发。由于所有变量(默认情况下)都是不可变的,因此你不需要担心线程会无意中更改变量值。

这并不是在说 Rust 中的变量就像常量一样,因为它们确实不是常量。变量可以被显式地定义为可变的。这样的变量称为 可变变量

这是在 Rust 中声明变量的语法:

// 默认情况下不可变
// 初始化值是**唯一**的值
let variable_name = value;

// 使用 'mut' 关键字定义可变变量
// 初始化值可以被改变
let mut variable_name = value;

? 尽管你可以改变可变变量的值,但你不能将另一种数据类型的值赋值给它。

这意味着,如果你有一个可变的浮点型变量,你不能在后面将一个字符赋值给它。

Rust 数据类型概观

在上一篇文章中,你可能注意到了我提到 Rust 是一种强类型语言。但是在定义变量时,你不需要指定数据类型,而是使用一个通用的关键字 let

Rust 编译器可以根据赋值给变量的值推断出变量的数据类型。但是如果你仍然希望明确指定数据类型并希望注释类型,那么可以这样做。以下是语法:

let variable_name: data_type = value;

下面是 Rust 编程语言中一些常见的数据类型:

  • 整数类型:分别用于有符号和无符号的 32 位整数的 i32u32
  • 浮点类型:分别用于 32 位和 64 位浮点数的 f32f64
  • 布尔类型bool
  • 字符类型char

我会在下一篇文章中更详细地介绍 Rust 的数据类型。现在,这应该足够了。

? Rust 并不支持隐式类型转换。因此,如果你将值 8 赋给一个浮点型变量,你将会遇到编译时错误。你应该赋的值是 8.8.0

Rust 还强制要求在读取存储在其中的值之前初始化变量。

{ // 该代码块不会被编译
    let a;
    println!("{}", a); // 本行报错
    // 读取一个**未初始化**变量的值是一个编译时错误
}

{ // 该代码块会被编译
    let a;
    a = 128;
    println!("{}", a); // 本行不会报错
    // 变量 'a' 有一个初始值
}

如果你在不初始化的情况下声明一个变量,并在给它赋值之前使用它,Rust 编译器将会抛出一个 编译时错误

虽然错误很烦人,但在这种情况下,Rust 编译器强制你不要犯写代码时常见的错误之一:未初始化的变量。

Rust 编译器的错误信息

来写几个程序,你将

  • 通过执行“正常”的任务来理解 Rust 的设计,这些任务实际上是内存相关问题的主要原因
  • 阅读和理解 Rust 编译器的错误/警告信息

测试变量的不可变性

让我们故意写一个试图修改不可变变量的程序,看看接下来会发生什么。

fn main() {
    let mut a = 172;
    let b = 273;
    println!("a: {a}, b: {b}");

    a = 380;
    b = 420;
    println!("a: {}, b: {}", a, b);
}

直到第 4 行看起来都是一个简单的程序。但是在第 7 行,变量 b —— 一个不可变变量 —— 的值被修改了。

注意打印 Rust 变量值的两种方法。在第 4 行,我将变量括在花括号中,以便打印它们的值。在第 8 行,我保持括号为空,并使用 C 的风格将变量作为参数。这两种方法都是有效的。(除了修改不可变变量的值,这个程序中的所有内容都是正确的。)

来编译一下!如果你按照上一章的步骤做了,你已经知道该怎么做了。

$ rustc main.rs
error[E0384]: cannot assign twice to immutable variable `b`
 --> main.rs:7:5
  |
3 |     let b = 273;
  |         -
  |         |
  |         first assignment to `b`
  |         help: consider making this binding mutable: `mut b`
...
7 |     b = 420;
  |     ^^^^^^^ cannot assign twice to immutable variable

error: aborting due to previous error

For more information about this error, try `rustc --explain E0384`.
? “binding” 一词是指变量名。但这只是一个简单的解释。

这很好的展示了 Rust 强大的错误检查和信息丰富的错误信息。第一行展示了阻止上述代码编译的错误信息:

error[E0384]: cannot assign twice to immutable variable b

这意味着,Rust 编译器注意到我试图给变量 b 重新赋值,但变量 b 是一个不可变变量。所以这就是导致这个错误的原因。

编译器甚至可以识别出错误发生的确切行和列号。

在显示 first assignment to b 的行下面,是提供帮助的行。因为我正在改变不可变变量 b 的值,所以我被告知使用 mut 关键字将变量 b 声明为可变变量。

?️ 自己实现一个修复来更好地理解手头的问题。

使用未初始化的变量

现在,让我们看看当我们尝试读取未初始化变量的值时,Rust 编译器会做什么。

fn main() {
    let a: i32;
    a = 123;
    println!("a: {a}");

    let b: i32;
    println!("b: {b}");
    b = 123;
}

这里,我有两个不可变变量 ab,在声明时都没有初始化。变量 a 在其值被读取之前被赋予了一个值。但是变量 b 的值在被赋予初始值之前被读取了。

来编译一下,看看结果。

$ rustc main.rs
warning: value assigned to `b` is never read
 --> main.rs:8:5
  |
8 |     b = 123;
  |     ^
  |
  = help: maybe it is overwritten before being read?
  = note: `#[warn(unused_assignments)]` on by default

error[E0381]: used binding `b` is possibly-uninitialized
 --> main.rs:7:19
  |
6 |     let b: i32;
  |         - binding declared here but left uninitialized
7 |     println!("b: {b}");
  |                   ^ `b` used here but it is possibly-uninitialized
  |
  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to previous error; 1 warning emitted

For more information about this error, try `rustc --explain E0381`.

这里,Rust 编译器抛出了一个编译时错误和一个警告。警告说变量 b 的值从来没有被读取过。

但是这是荒谬的!变量 b 的值在第 7 行被访问了。但是仔细看;警告是关于第 8 行的。这很令人困惑;让我们暂时跳过这个警告,继续看错误。

这个错误信息说 used binding b is possibly-uninitialized。和之前的例子一样,Rust 编译器指出错误是由于尝试在第 7 行读取变量 b 的值而引起的。读取变量 b 的值是错误的原因是它的值没有初始化。在 Rust 编程语言中,这是非法的。因此编译时错误出现。

?️ 这个错误可以很容易地通过交换第 7 和第 8 行的代码来解决。试一下,看看错误是否消失了。

示例程序:交换数字

现在你已经熟悉了常见的变量相关问题,让我们来看一个交换两个变量值的程序。

fn main() {
    let mut a = 7186932;
    let mut b = 1276561;

    println!("a: {a}, b: {b}");

    // 交换变量值
    let temp = a;
    a = b;
    b = temp;

    println!("a: {}, b: {}", a, b);
}

我在这里声明了两个变量 ab。这两个变量都是可变的,因为我希望在后面改变它们的值。我赋予了一些随机值。最初,我打印了这些变量的值。

然后,在第 8 行,我创建了一个名为 temp 的不可变变量,并将存储在 a 中的值赋给它。之所以这个变量是不可变的,是因为 temp 的值不会改变。

要交换值,我将变量 b 的值赋给变量 a,在下一行,我将 temp 的值(它包含 a 的值)赋给变量 b。现在值已经交换了,我打印了变量 ab 的值。

在编译并执行上面的代码后,我得到了以下输出:

a: 7186932, b: 1276561
a: 1276561, b: 7186932

正如你所见,值已经交换了。完美。

使用未使用的变量

当你声明了一些变量,打算在后面使用它们,但是还没有使用它们,然后编译你的 Rust 代码来检查一些东西时,Rust 编译器会警告你。

原因是显而易见的。不会被使用的变量占用了不必要的初始化时间(CPU 周期)和内存空间。如果不会被使用,为什么要在程序写上它呢?尽管编译器确实会优化这一点。但是它仍然是一个问题,因为它会以多余的代码的形式影响可读性。

但是,有的时候,你可能会面对这样的情况:创建一个变量与否不在你的控制之下。比如说,当一个函数返回多个值,而你只需要其中的一些值时。在这种情况下,你不能要求库维护者根据你的需要调整他们的函数。

所以,在这种情况下,你可以写一个以下划线开头的变量,Rust 编译器将不再显示这样的警告。如果你真的不需要使用存储在该未使用变量中的值,你可以简单地将其命名为 _(下划线),Rust 编译器也会忽略它!

接下来的程序不仅不会生成任何输出,而且也不会生成任何警告和/或错误消息:

fn main() {
    let _unnecessary_var = 0; // 没有警告
    let _ = 0.0; // 完全忽略
}

算术运算

数学就是数学,Rust 并没有在这方面创新。你可以使用在其他编程语言(如 C、C++ 和/或 Java)中使用过的所有算术运算符。

包含可以在 Rust 编程语言中使用的所有运算符和它们的含义的完整列表可以在 这里 找到。

示例程序:一个生锈的温度计

(LCTT 译注:这里的温度计“生锈”了是因为它是使用 Rust(生锈)编写的,原作者在这里玩了一个双关。)

接下来是一个典型的程序,它将华氏度转换为摄氏度,反之亦然。

fn main() {
    let boiling_water_f: f64 = 212.0;
    let frozen_water_c: f64 = 0.0;

    let boiling_water_c = (boiling_water_f - 32.0) * (5.0 / 9.0);
    let frozen_water_f = (frozen_water_c * (9.0 / 5.0)) + 32.0;

    println!(
        "Water starts boiling at {}°C (or {}°F).",
        boiling_water_c, boiling_water_f
    );
    println!(
        "Water starts freezing at {}°C (or {}°F).",
        frozen_water_c, frozen_water_f
    );
}

没什么大不了的……华氏温度转换为摄氏温度,反之亦然。

正如你在这里看到的,由于 Rust 不允许自动类型转换,我不得不在整数 32、9 和 5 后放一个小数点。除此之外,这与你在 C、C++ 和/或 Java 中所做的类似。

作为练习,尝试编写一个程序,找出给定数中有多少位数字。

常量

如果你有一些编程知识,你可能知道这意味着什么。常量是一种特殊类型的变量,它的值永远不会改变它保持不变

在 Rust 编程语言中,使用以下语法声明常量:

const CONSTANT_NAME: data_type = value;

如你所见,声明常量的语法与我们在 Rust 中看到的变量声明非常相似。但是有两个不同之处:

  • 常量的名字需要像 SCREAMING_SNAKE_CASE 这样。所有的大写字母和单词之间用下划线分隔。
  • 常量的数据类型必须被显性定义。

变量与常量的对比

你可能在想,既然变量默认是不可变的,为什么语言还要包含常量呢?

接下来这个表格应该可以帮助你消除疑虑。(如果你好奇并且想更好地理解这些区别,你可以看看我的博客,它详细地展示了这些区别。)

一个展示 Rust 编程语言中变量和常量之间区别的表格

使用常量的示例程序:计算圆的面积

这是一个很直接的关于 Rust 中常量的简单程序。它计算圆的面积和周长。

fn main() {
    const PI: f64 = 3.14;
    let radius: f64 = 50.0;

    let circle_area = PI * (radius * radius);
    let circle_perimeter = 2.0 * PI * radius;

    println!("有一个周长为 {radius} 厘米的圆");
    println!("它的面积是 {} 平方厘米", circle_area);
    println!(
        "以及它的周长是 {} 厘米",
        circle_perimeter
    );
}

如果运行代码,将产生以下输出:

有一个周长为 50 厘米的圆
它的面积是 7850 平方厘米
以及它的周长是 314 厘米

Rust 中的变量遮蔽

如果你是一个 C++ 程序员,你可能已经知道我在说什么了。当程序员声明一个与已经声明的变量同名的新变量时,这就是变量遮蔽。

与 C++ 不同,Rust 允许你在同一作用域中执行变量遮蔽!

? 当程序员遮蔽一个已经存在的变量时,新变量会被分配一个新的内存地址,但是使用与现有变量相同的名称引用。

来看看它在 Rust 中是如何工作的。

fn main() {
    let a = 108;
    println!("a 的地址: {:p}, a 的值 {a}", &a);
    let a = 56;
    println!("a 的地址: {:p}, a 的值: {a} // 遮蔽后", &a);

    let mut b = 82;
    println!("\nb 的地址: {:p}, b 的值: {b}", &b);
    let mut b = 120;
    println!("b的地址: {:p}, b的值: {b} // 遮蔽后", &b);

    let mut c = 18;
    println!("\nc 的地址: {:p}, c的值: {c}", &c);
    c = 29;
    println!("c 的地址: {:p}, c的值: {c} // 遮蔽后", &c);
}

println 语句中花括号内的 :p 与 C 中的 %p 类似。它指定值的格式为内存地址(指针)。

我在这里使用了 3 个变量。变量 a 是不可变的,并且在第 4 行被遮蔽。变量 b 是可变的,并且在第 9 行也被遮蔽。变量 c 是可变的,但是在第 14 行,只有它的值被改变了。它没有被遮蔽。

现在,让我们看看输出。

a 的地址: 0x7ffe954bf614, a 的值 108
a 的地址: 0x7ffe954bf674, a 的值: 56 // 遮蔽后

b 的地址: 0x7ffe954bf6d4, b 的值: 82
b 的地址: 0x7ffe954bf734, b 的值: 120 // 遮蔽后

c 的地址: 0x7ffe954bf734, c 的值: 18
c 的地址: 0x7ffe954bf734, c 的值: 29 // 遮蔽后

来看看输出,你会发现不仅所有三个变量的值都改变了,而且被遮蔽的变量的地址也不同(检查十六进制的最后几个字符)。

变量 ab 的内存地址改变了。这意味着变量的可变性或不可变性并不是遮蔽变量的限制。

总结

本文介绍了 Rust 编程语言中的变量和常量。还介绍了算术运算。

做个总结:

  • Rust 中的变量默认是不可变的,但是可以引入可变性。
  • 程序员需要显式地指定变量的可变性。
  • 常量总是不可变的,无论如何都需要类型注释。
  • 变量遮蔽是指使用与现有变量相同的名称声明一个 变量。

很好!我相信和 Rust 一起的进展不错。在下一章中,我将讨论 Rust 中的数据类型。敬请关注。

与此同时,如果你有任何问题,请告诉我。

(题图:MJ/7c5366b8-f926-487e-9153-0a877145ca5)


via: https://itsfoss.com/rust-variables/

作者:Pratham Patel 选题:lkxed 译者:Cubik 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

一个有趣的、开源的私人通讯软件,它利用 Tor 来保证你的通信安全和私密。

Speek 是一种网络通讯服务,它利用多种技术保证网络聊天的私密性。

它是端到端加密的、去中心化和开源的。

毫无疑问,它的目标是将自己定位为 WhatsApp 的替代品 之一和 Linux 上的 Signal 的竞争对手。

那么,它到底如何呢? 让我们一起来仔细看看细节。

Speek! 适用于 Linux 和安卓的点对点即时消息应用程序

(LCTT 译注: 点对点 Peer-to-Peer ,又称对等式网络,是无中心服务器、依靠 对等点 peer 交换信息的互联网络体系,它的作用在于,减低以往网路传输中的节点,以降低资料遗失的风险。)

Speek! 截图

Speek!(名称中带有感叹号)是一种加密的聊天软件,旨在抗审查,同时保护数据隐私。

你也可以认为它是 Session 的替代品,但有一些区别。

与其他可用的通讯软件相比,它是一个相当新的竞争对手。但是如果你想尝试开源的解决方案,它应该作为候选者。

虽然 Speek! 声称能保持匿名,但如果你需要的是完全匿名的话,你应该始终注意自己在设备上的活动。因为你需要考虑的不仅仅是即时通讯软件。

Speek! id

Speek! 利用去中心化的 Tor 网络来保证安全性和私密性。不仅如此,这样做可以让你无需电话号码就能使用服务。你只需要你的 Speek ID 就可以与人联系,而别人很难知道你的 ID。

(LCTT 译注:Tor 是一个可以实现匿名通信的自由软件。其名源于 洋葱路由器 The Onion Router 的英文缩写。)

Speek! 的亮点

Speek! 选项

Speek! 的一些主要亮点包括:

  • 端到端加密:除收件人外,没有人可以查看你的消息。
  • 通过 TOR 路由流量:使用 TOR 路由消息,增强隐私。
  • 没有中央服务器:增加了对审查的抵抗力,因为很难关闭服务。此外,没有针对黑客的单一攻击点。
  • 无需注册:无需共享任何个人信息即可开始使用该服务。你只需要一个公钥来识别/添加用户。
  • 自毁聊天:当你关闭应用程序时,消息会自动删除。增添了额外的隐私和安全性。
  • 无元数据:它会在你交换消息时消除任何元数据。
  • 私人文件共享:你还可以使用该服务安全地共享文件。

下载适用于 Linux 和其他平台的 Speek!

你可以从他们的 官方网站 下载 Speek!。

在撰写本文时,Speek! 仅适用于 Linux、安卓、macOS 和 Windows。

对于 Linux,你会找到一个 AppImage 文件。如果你不知道 AppImage,可以参考我们的 AppImage 指南 来运行该应用程序。

安卓系统上的 Speek!

另外,Google Play 商店 上的安卓应用程序还比较早期。因此,你在尝试使用它时可以期待一下它的改进。

Speek!

关于 Speek! 的用户体验

这个应用的用户体验非常令人满意,包含了所有必备的功能。它可以更好,但已经很不错了。

嗯,关于 Speek! 的 GUI 没什么好说的。GUI 非常极简风。它的核心是一个聊天应用程序,而它做得恰如其分。没有动态,没有附近的人,没有不必要的附加组件。

在我使用这个应用程序的有限时间里,我很满意它的功能。它提供的功能使其成为一款出色的聊天应用程序,可通过其背后的所有技术提供安全和私密的消息传递体验。

如果将它与一些商业上更成功的聊天软件进行比较,它在功能上存在不足。但话又说回来,Speek! 的设计就不是一个只关注用户体验的时尚聊天应用。

因此,我只向注重隐私的用户推荐 Speek!。如果你想要平衡用户体验和功能,你可能希望继续使用像 Signal 这样的私人聊天软件。

你对于 Speek 又有什么看法?对于注重隐私的用户来说,它是一个很好的私人聊天软件吗? 请在下方评论区告诉我你的想法。

(题图:MJ:A girl looks at her phone while two cats are peeking at her phone next to her.)


via: https://itsfoss.com/speek/

作者:Pratham Patel 选题:lkxed 译者:XiaotingHuang22 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出