Matering_Rust(译):让你的脚湿透,第一章(未完待续

由于您已经是一位成熟的程序员,本章将以相当快速的方式介绍Rust的设计理念和语言的基础知识。 每个子部分将包含编译器的示例代码和运行,它给出的输出(如果有的话),并且将有十几个代码示例。

编程是知识和工艺的独特组合,两者同样重要。 为了掌握掌握工艺的道路,你需要练习,这就是为什么我建议你手动编写,而不是复制/粘贴你在这里看到的每一段代码。

以下是本章涉及的主题:

  • 安装Rust编译器和Cargo构建工具
  • 语言特征:变量,条件,循环,基元类型,复合类型和序列
  • 使用编译器磨练技能的最后练习

什么是Rust你为什么要关心?

Rust是一种编程语言,最初由Graydon Hoare于2006年创建。 它目前是一个开源项目,主要由Mozilla和其他开发人员组成的团队开发。 第一个稳定版本1.0于2015年发布。

作为一种通用语言,它的目标是C和C ++占主导地位的空间。 它的许多设计决策强调的定义原则是零成本抽象和编译器辅助的资源安全。

在Rust的迭代器中可以看到零成本抽象的一个例子。 它们是遍历序列的循环的抽象,与Ruby等显着更高级别的语言大致相同。 但是,它们的运行时成本为零; 他们编译成相同(或更好)的汇编代码,就像你手工编写相同的循环一样。

资源安全意味着在Rust代码中,编译器可以分析您的资源(内存,文件句柄和数据库引用)是否安全。 C程序中最典型的错误是内存访问错误,其中内存在被释放或忘记释放后使用。 在其他语言中,您可能会通过自动垃圾收集避免内存错误,但这可能会或可能不会帮助您使用其他类型的资源,例如文件指针。 如果引入并发和共享内存,情况会更糟。

Rust有一个借用和生命的系统; 另外,它取代了带有错误类型的空指针的概念。 这些决定提高了语言的复杂性,但却无法做出许多错误。

最后但同样重要的是,Rust的社区非常活跃和友好。 2016年Stack Overflow的开发者调查选择它作为最受欢迎的编程语言,因此可以说整个编程社区对它非常感兴趣。

总而言之,你应该关心Rust,因为你可以编写具有较少错误的高性能软件,同时享受许多现代语言功能和一个非常棒的社区!

安装Rust编译器和Cargo

Rust工具集有两个主要组件:编译器(rustc)和组合构建工具或依赖管理器(Cargo)。 此工具集有三个经常发布的版本:

  • Nightly:这是主开发分支的每日成功构建。 这包含所有功能,其中一些功能不稳定。
  • Beta:这是每六周发布一次; 从夜间开始新的测试分支。 它仅包含标记为稳定的功能。
  • Stable:每六周发布一次; 之前的beta分支成为新的稳定。

    鼓励开发人员主要使用stable版的。 但是,nightly 版本启用了许多有用的功能,这就是某些库和程序需要它的原因。

使用rustup.rs

为了让各种平台上的人们能够更轻松地下载和安装标准工具,Rust团队开发了一个生锈工具。 rustup工具提供了一种方法,可以为您的本地用户轻松安装Rust工具集(rustc和Cargo)的预构建二进制文件。 它还允许安装各种其他组件,例如Rust源代码和文档。官方支持的安装Rust的方法是使用rustup.rs

curl https://sh.rustup.rs -sSf | sh

此命令将下载安装程序并运行它。 默认情况下,安装程序将安装Rust编译器的稳定版本,Cargo构建工具和API文档。 它们默认安装在.cargo目录下的当前用户,而rustup也会更新您的PATH环境变量以指向那里。

以下是运行命令的方式:

Matering_Rust(译):让你的脚湿透,第一章(未完待续

如果您需要对安装进行任何更改,请选择2.但是这些默认设置对我们来说没问题,因此我们将继续选择1.这是输出应该如下所示:

Matering_Rust(译):让你的脚湿透,第一章(未完待续

现在,您应该拥有编译和运行Rust编写的程序所需的一切。 我们来试试吧!

参观语言并尝试一下

对于基本的语言功能,Rust不会偏离你习惯的东西。 程序在模块中定义; 它们包含函数,变量和复合数据结构。 以下是最小程序的外观:

fn main() {
println!("Are you writing this or reading it?");
}

尝试编译并运行它。 将其写入名为main.rs的文件,然后运行Rust编译器:

> rustc -o main main.rs
> ./main
Are you writing this or reading it?

手动运行rustc不是你将如何为真正的程序执行,但它将为这些小程序。 运行小段代码的一个很好的替代方法是使用http://play.rust-lang.org中的Rust Playground服务:Matering_Rust(译):让你的脚湿透,第一章(未完待续

程序本身非常简单:fn关键字用于定义函数,后跟函数名称,括号内的参数以及花括号内的函数体。 那里没有新的东西(除了一些语法)。 打印行调用后的感叹号表示它实际上不是函数,而是宏。 这只是意味着它在编译时执行一些扩展而不是在运行时完成所有工作。 如果您熟悉其他语言(如C或LISP)的宏,那么Rust宏也会很熟悉。 第9章编译器插件将详细介绍宏。

变量使用let关键字定义。 Rust有一个本地类型推断,这意味着编译器会计算出函数变量的类型,编码器几乎总能省略它们。 它可以很容易地提高源代码的可读性,特别是在经常使用的静态字符串的情况下:

// first-program.rs
fn main() {
let target_inferred = "inferred world";
// these two variables
let target: &'static str = "non-inferred world"; // have identical types
println!("Hi there, {}", target_inferred);
println!("Hi there, {}", target);
}

此程序中的字符串是字符串文字,或者更具体地说,是具有静态生命周期的字符串切片。 字符串将在第6章,内存,生命周期和借用中的第4章,类型和生命周期中介绍。

代码中的注释类似于C,//用于单行注释,而/ * * /块用于多行注释。

常量和变量

Rust通过使常量成为默认变量类型而偏离主流。 如果需要一个可以变异的变量,可以使用let mut关键字:

// variables.rs
fn main() {
let mut target = "world";
println!("Howdy, {}", target);
target = "mate";
println!("Howdy, {}", target);
}

条件也应该看起来很熟悉; 他们遵循C-like if … else模式。 由于Rust是强类型的,因此条件必须是布尔类型:

// conditionals.rs
fn main() {
let condition = true;
if condition {
println!("Condition was true");
} else {
println!("Condition was false");
}
}

在Rust中,if不是语句而是表达式。 这种区别意味着如果总是返回一个值。 该值可以是您不必使用的空类型,也可以是实际值。 这意味着您可以使用if表达式,因为在某些语言中使用了高级表达式:

// if-expression.rs
fn main() {
let result = if 1 == 2 {
"Nothing makes sense"
} else {
"Sanity reigns"
};
println!("Result of computation: {}", result);
}

仔细看看前面的程序; 它突出了有关分号和块的重要细节。 分号在Rust中不是可选的,但它具有特定含义。 块的最后一个表达式是从块中返回其值的表达式,并且最后一行中没有分号是重要的; 如果我们要在if块中的字符串之后添加分号,Rust会将其解释为您想要抛弃该值:

// semicolon.rs
fn main() {
let result = if 1 == 2 {
"Nothing makes sense";
} else {
"Sanity reigns";
};
println!("Result of computation: {:?}", result);
}

在这种情况下,结果将为空,这就是我们必须更改println的原因! 表达轻微; 这种类型不能以常规方式打印出来。 更多关于第4章,类型的内容,我们讨论类型。

循环

使用while循环(如果需要循环的条件)或循环(如果不需要条件)编程简单循环。 break关键字让你脱离循环。 以下是使用loop关键字的示例:

// loop.rs
fn main() {
let mut x = 1000;
loop {
if x < 0 {
break;
} p
rintln!("{} more runs to go", x);
x -= 1;
}
}

while循环的示例如下:

// while.rs
fn main() {
let mut x = 1000;
while x > 0 {
println!("{} more runs to go", x);
x -= 1;
}
}

复合数据

为了定义自定义数据类型,有结构。 更简单的形式称为元组结构,其中各个字段未命名但由其位置引用。 当您的数据仅包含一个或几个字段以实现更好的类型安全级别时,通常应使用此选项,例如:

// tuplestruct.rs
#[derive(PartialEq)]
struct Fahrenheit(i64);
#[derive(PartialEq)]
struct Celsius(i64);
fn main() {
let temperature1 = Fahrenheit(10);
let temperature2 = Celsius(10);
println!("Is temperature 1 the same as temperature 2? Answer: {}",
temperature1 == temperature2);
println!("Temperature 1 is {} fahrenheit", temperature1.0);
println!("Temperature 2 is {} celsius", temperature2.0);
}

可以通过。操作访问元组结构内部的内容,其中数字指的是结构中字段的位置。

这是本书中第一段无法编译的代码,原因是虽然两个温度得到了为它们导出的等于方法,但它们只会被定义用于比较相同的类型。 由于将华氏温度与摄氏温度进行比较而没有任何转换没有意义,您可以通过删除最后一个println来修复这段代码! 调用或通过比较temperature1与自身。 结构体之前的派生行生成允许==操作针对相同类型工作的代码。

以下是编译器如何告诉您:

Matering_Rust(译):让你的脚湿透,第一章(未完待续

另一种形式的结构已命名字段:

// struct.rs
struct Character {
strength: u8,
dexterity: u8,
constitution: u8,
wisdom: u8,
intelligence: u8,
charisma: u8,
name: String
} f
n main() {
let char = Character { strength: 9, dexterity: 9, constitution: 9,
wisdom: 9, intelligence: 9, charisma: 9,
name: "Generic AD&D Hero".to_string() };
println!("Character's name is {}, and his/her strength is {}", char.name, char.strength);
}

在前面的结构中,您可以看到基本类型的用法,即无符号的8位整数(u8)。 按照约定的原始类型以小写字符开头,而其他类型以大写字母开头(例如String up)。 作为参考,这里是所有原始类型的完整表:

Matering_Rust(译):让你的脚湿透,第一章(未完待续

Matering_Rust(译):让你的脚湿透,第一章(未完待续

枚举和模式匹配

每当你需要为几种不同类型的东西建模时,枚举可能是一个不错的选择。 Rust中的枚举变体可以在其中包含或不包含数据的情况下定义,数据字段可以是命名的或匿名的:

enum Direction {
N,
NE,
E,
SE,
S,
SW,
W,
NW
} e
num PlayerAction {
Move(direction: Direction, speed: u8),
Wait,
Attack(Direction)
}

这定义了两种枚举类型:Direction和PlayerAction。 对于这些枚举类型中的每一个,这也定义了许多命名空间的枚举变体:Direction :: N,Direction :: NE,以及Direction类型,以及PlayerAction :: Move,PlayerAction :: Wait和PlayerAction :: 攻击PlayerAction类型。 使用枚举的最典型方法是使用匹配表达式进行模式匹配:

#[derive(Debug)]
enum Direction {
N,
NE,
E,
SE,
S,
SW,
W,
NW,
} e
num PlayerAction {
Move {
direction: Direction,
speed: u8,
},
Wait,
Attack(Direction),
} f
n main() {
let simulated_player_action = PlayerAction::Move {
direction: Direction::NE,
speed: 2,
};
match simulated_player_action {
PlayerAction::Wait => println!("Player wants to wait"),
PlayerAction::Move { direction, speed } => {
println!("Player wants to move in direction {:?} with speed {}",
direction, speed)
} P
layerAction::Attack(direction) => {
println!("Player wants to attack direction {:?}", direction)
}
};
}

就像if一样,match也是一个表达式,这意味着它返回一个值,并且该值必须在每个分支中都是相同的类型。 在前面的示例中,它是println!()返回的内容,即空类型。

第一个枚举上方的派生行告诉编译器为Debug特征生成代码。 第4章“类型”将更多地介绍特征,但是现在,我们可以注意到它会使println成为现实! macro的{:?}语法正常工作。 编译器告诉我们是否缺少Debug特性并提供有关如何修复它的建议:

Matering_Rust(译):让你的脚湿透,第一章(未完待续

结构方法

通常情况下,您希望编写对特定结构进行操作的函数或返回特定结构的值。 那是你用impl关键字编写实现块的时候。

例如,我们可以使用两种方法扩展先前定义的字符结构:一个带有名称并为所有字符属性设置默认值的构造函数和一个用于字符强度的getter方法:

// structmethods.rs
struct Character {
strength: u8,
dexterity: u8,
constitution: u8,
wisdom: u8,
intelligence: u8,
charisma: u8,
name: String,
} i
mpl Character {
fn new_named(name: String) -> Character {
Character {
strength: 9,
constitution: 9,
dexterity: 9,
wisdom: 9,
intelligence: 9,
charisma: 9,
name: name,
}
} f
n get_strength(&self) -> u8 {
self.strength
}
}

new_named方法称为关联函数,因为它不将self作为第一个参数。 它与许多其他语言称之为静态方法的距离不远。 它也是一个构造函数方法,因为它遵循以单词new开头的约定,因为它返回一个我们定义实现的相同类型(Character)的结构。 由于new_named是一个关联函数,因此可以通过为struct name和double冒号添加前缀来调用它:

Character::new_named("Dave")


get_strength中的self参数是特殊的,因为它的类型被推断为与impl块的类型相同,并且因为它使get_strength成为结构上的可调用方法。 换句话说,可以在已创建的struct实例上调用get_strength:

let character = Character::new_named("Dave");
character.get_strength();

自我之前的&符号意味着在方法的持续时间内借用自我,这正是我们想要的。 没有&符号,所有权将被移动到方法,这意味着在离开get_strength之后将释放该值。 所有权是Rust的特色,将在第6章,记忆,生命周期和借阅中深入探讨。

使用模块中的其他代码片段

快速了解如何将代码从其他地方包含到您正在编写的模块中。 Rust的模块系统有自己的特点,但现在已经足够注意use语句将来自另一个模块的代码带入当前的命名空间。 它不会加载外部代码,只会改变事物的可见性:

// use.rs
use std::ascii::AsciiExt;
fn main() {
let lower_case_a = 'a';
let upper_case_a = lower_case_a.to_ascii_uppercase();
println!("{} upper cased is {}", lower_case_a, upper_case_a);
}

在此示例中,AsciiExt模块包含char类型的to_ascii_uppercase的实现,因此在此模块中包含该实现可以在此处使用此方法。 如果你错过了一个特定的use语句,编译器会再次管理,如果我们删除第一行并尝试编译时会发生什么:

Matering_Rust(译):让你的脚湿透,第一章(未完待续