机器之心报道

参与:思、Jamin

据说很多开发者一天入门 Python,两天上手 Go,但到了 Rust 就会发现画风隐约有些不对。它从语法到特性,似乎都要复杂一些。本文介绍的就是 Rust,作者表示,通过解析大量代码,「半个小时」就能入门 Rust。

Rust 是一门系统编程语言,专注于安全,尤其是并发安全。它支持函数式和命令式以及泛型等编程范式的多范式语言,且 TensorFlow 等深度学习框架也把它作为一个优秀的前端语言。

Rust 在语法上和 C、C++类似,都由花括弧限定代码块,并有相同的控制流关键字,但 Rust 设计者想要在保证性能的同时提供更好的内存安全。Rust 自 2016 年就已经开源了,在各种开发者调查中,它也总能获得「最受欢迎的语言」这一称赞,目前该开源项目已有 42.9K 的 Star 量。

机器之心的读者大多数都非常熟悉 Python,而 Rust 就没那么熟悉了。在 Amos 最近的一篇博文中,他表示如果阅读他的作品,我们半个小时就能入门 Rust。因此在这篇文章中,我们将介绍该博文的主要内容,它并不关注于 1 个或几个关键概念,相反它希望通过代码块纵览 Rust 的各种特性,包括各种关键词与符号的意义。

在 HackNews 上,很多开发者表示这一份入门教程非常实用,Rust 的入门门槛本来就比较高,如果再介绍各种复杂的概念与特性,很容易出现「从入门到劝退」。因此这种从实例代码出发的教程,非常有意义。

从变量说起

let 能绑定变量:

let x; // declare "x"x = 42; // assign 42 to "x"let x = 42; // combined in one line

可以使用 :来制定变量的数据类型,以及数据类型注释:

let x: i32; // `i32` is a signed 32-bit integerx = 42;// there's i8, i16, i32, i64, i128// also u8, u16, u32, u64, u128 for unsignedlet x: i32 = 42; // combined in one line

如果你声明一个变量并在初始化之前就调用它,编译器会报错:

let x;foobar(x); // error: borrow of possibly-uninitialized variable: `x`x = 42;

然而,这样做完全没问题:

let x;x = 42;foobar(x); // the type of `x` will be inferred from here

下划线表示特殊的命名,或者更确切地说是「缺失的命名」,它和 Python 的用法有点像:

// this does *nothing* because 42 is a constantlet _ = 42;// this calls `get_thing` but throws away its resultlet _ = get_thing();

以下划线开头的命名是常规命名,只是编译器不会警告它们未被使用:

// we may use `_x` eventually, but our code is a work-in-progress// and we just wanted to get rid of a compiler warning for now.let _x = 42;

相同命名的单独绑定是可行的,第一次绑定的变量会取消:

let x = 13;let x = x + 3;// using `x` after that line only refers to the second `x`,// the first `x` no longer exists.

Rust 有元组类型,可以将其看作是「不同数据类型值的定长集合」。

let pair = ('a', 17);pair.0; // this is 'a'pair.1; // this is 17

如果真的想配置 pair 的数据类型,可以这么写:

let pair: (char, i32) = ('a', 17);

元组在赋值时可以被拆解,这意味着它们被分解成各个字段:

let (some_char, some_int) = ('a', 17);// now, `some_char` is 'a', and `some_int` is 17

当一个函数返还一个元组时会非常有用:

let (left, right) = slice.split_at(middle);

当然,在解构一个元组时,可以只分离它的一部分:

let (_, right) = slice.split_at(middle);

分号表示语句的结尾:

let x = 3;let y = 5;let z = y + x;

不加分号意味着语句可以跨多行:

let x = vec![1, 2, 3, 4, 5, 6, 7, 8] .iter() .map(|x| x + 3) .fold(0, |x, y| x + y);

函数来了

fn 声明一个函数。下面是一个空函数:

fn greet() { println!("Hi there!");}

这是一个返还 32 位带符号整数值的函数。箭头表示返还类型:

fn fair_dice_roll() -> i32 { 4}

花括号表示了一个代码块,且拥有其自己的作用域:

// This prints "in", then "out"fn main() { let x = "out"; { // this is a different `x` let x = "in"; println!(x); } println!(x);}

代码块也是表示式,表示其计算为一个值。

// this:let x = 42;// is equivalent to this:let x = { 42 };

在一个代码块中,可以有多个语句:

let x = { let y = 1; // first statement let z = 2; // second statement y + z // this is the *tail* - what the whole block will evaluate to};

这也是为什么「省略函数末尾的分号」等同于加上了 Retrun,这些都是等价的:

fn fair_dice_roll() -> i32 { return 4;}fn fair_dice_roll() -> i32 { 4}

if 条件语句也是表达式:

fn fair_dice_roll() -> i32 { if feeling_lucky { 6 } else { 4 }}

match 匹配器也是一个表达式:

fn fair_dice_roll() -> i32 { match feeling_lucky { true => 6, false => 4, }}

Dots 通常用于访问某个对象的字段:

let a = (10, 20);a.0; // this is 10let amos = get_some_struct();amos.nickname; // this is "fasterthanlime"

或者调用对象的方法:

let nick = "fasterthanlime";nick.len(); // this is 14

双冒号与此类似,但可对命名空间进行操作。在此举例中,std 是一个 crate (~ a library),cmp 是一个 module(~ a source file),以及 min 是个函数:

let least = std::cmp::min(3, 8); // this is 3

use 指令可用于从其他命名空间中「引入范围」命名:

use std::cmp::min;let least = min(7, 1); // this is 1

在 use 指令中,花括号还有另一个含义:「globs」,因此可以同时导入 min 以及 max:

// this works:use std::cmp::min;use std::cmp::max;// this also works:use std::cmp::{min, max};// this also works!use std::{cmp::min, cmp::max};

通配符(*)允许从命名空间导入符号:

// this brings `min` and `max` in scope, and many other thingsuse std::cmp::*;

Types 也是命名空间和方法,它可以作为常规函数调用:

let x = "amos".len(); // this is 4let x = str::len("amos"); // this is also 4

str 是一个基元数据类型,但在默认情况下,许多非基元数据类型也在作用域中。

// `Vec` is a regular struct, not a primitive typelet v = Vec::new();// this is exactly the same code, but with the *full* path to `Vec`let v = std::vec::Vec::new()

至于为什么可行,因为 Rust 在每个模块的开头都插入了:

use std::prelude::v1::*;

再说说结构体

使用 struct 关键字声明结构体:

struct Vec2 { x: f64, // 64-bit floating point, aka "double precision" y: f64,}

可以使用结构语句初始化:

let v1 = Vec2 { x: 1.0, y: 3.0 };let v2 = Vec2 { y: 2.0, x: 4.0 };// the order does not matter, only the names do

有一个快捷方式可以从另一个结构体初始化本结构体的其余字段:

let v3 = Vec2 { x: 14.0, ..v2};

这就是所谓的「结构体更新语法」只能发生在最后一个位置,不能在其后面再跟一个逗号。

注意其余字段可以表示所有字段:

let v4 = Vec2 { ..v3 };

结构体与元组一样,可以被解构。例如一个有效的 let 模式:

let (left, right) = slice.split_at(middle);let v = Vec2 { x: 3.0, y: 6.0 };let Vec2 { x, y } = v;// `x` is now 3.0, `y` is now `6.0`let Vec2 { x, .. } = v;// this throws away `v.y`

让 let 模式在 if 里可以作为条件:

struct Number { odd: bool, value: i32,}fn main() { let one = Number { odd: true, value: 1 }; let two = Number { odd: false, value: 2 }; print_number(one); print_number(two);}fn print_number(n: Number) { if let Number { odd: true, value } = n { println!("Odd number: {}", value); } else if let Number { odd: false, value } = n { println!("Even number: {}", value); }}// this prints:// Odd number: 1// Even number: 2

多分支的 match 也是条件模式,就像 if let:

fn print_number(n: Number) { match n { Number { odd: true, value } => println!("Odd number: {}", value), Number { odd: false, value } => println!("Even number: {}", value), }}// this prints the same as before

match 必须是囊括所有情况的的:至少需要匹配一个条件分支。

fn print_number(n: Number) { match n { Number { value: 1, .. } => println!("One"), Number { value: 2, .. } => println!("Two"), Number { value, .. } => println!("{}", value), // if that last arm didn't exist, we would get a compile-time error }}

fn print_number(n: Number) { match n.value { 1 => println!("One"), 2 => println!("Two"), _ => println!("{}", n.value), }}

Type 别名

我们可以使用 type 关键字声明另一类型的别名,然后就可以像使用一个真正的类型一样使用这种类型。例如定义 Name 这种数据类型为字符串,后面就可以直接使用 Name 这种类型了。你可以在方法中声明不同的数据类型:

struct Number { odd: bool, value: i32,}impl Number { fn is_strictly_positive(self) -> bool { self.value > 0 }}

fn main() { let minus_two = Number { odd: false, value: -2, }; println!("positive? {}", minus_two.is_strictly_positive()); // this prints "positive? false"}

fn main() { let n = Number { odd: true, value: 17, }; n.odd = false; // error: cannot assign to `n.odd`, // as `n` is not declared to be mutable}

fn main() { let n = Number { odd: true, value: 17, }; n = Number { odd: false, value: 22, }; // error: cannot assign twice to immutable variable `n`}

mut 可以使变量声明变为可变的:

fn main() { let mut n = Number { odd: true, value: 17, } n.value = 19; // all good}

Traits 描述的是多种数据类型的共同点:

trait Signed { fn is_strictly_negative(self) -> bool;}

impl Signed for Number { fn is_strictly_negative(self) -> bool { self.value < 0 }}fn main() { let n = Number { odd: false, value: -44 }; println!("{}", n.is_strictly_negative()); // prints "true"}

impl Signed for i32 { fn is_strictly_negative(self) -> bool { self < 0 }}fn main() { let n: i32 = -44; println!("{}", n.is_strictly_negative()); // prints "true"}

impl std::ops::Neg for Number { type Output = Self; fn neg(self) -> Self { Self { value: -self.value, odd: self.odd, } }}

fn main() { let a: i32 = 15; let b = a; // `a` is copied let c = a; // `a` is copied again}

下面的代码也是能运行的:

fn print_i32(x: i32) { println!("x = {}", x);}fn main() { let a: i32 = 15; print_i32(a); // `a` is copied print_i32(a); // `a` is copied again}

fn main() { let n = Number { odd: true, value: 51 }; let m = n; // `n` is moved into `m` let o = n; // error: use of moved value: `n`}

fn print_number(n: Number) { println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);}fn main() { let n = Number { odd: true, value: 51 }; print_number(n); // `n` is moved print_number(n); // error: use of moved value: `n`}

fn print_number(n: &Number) { println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);}fn main() { let n = Number { odd: true, value: 51 }; print_number(&n); // `n` is borrowed for the time of the call print_number(&n); // `n` is borrowed again}

fn invert(n: &mut Number) { n.value = -n.value;}fn print_number(n: &Number) { println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);}fn main() { // this time, `n` is mutable let mut n = Number { odd: true, value: 51 }; print_number(&n); invert(&mut n); // `n is borrowed mutably - everything is explicit print_number(&n);}

// note: `Copy` requires that `Clone` is implemented tooimpl std::clone::Clone for Number { fn clone(&self) -> Self { Self { ..*self } }}impl std::marker::Copy for Number {}

现在 Clone 仍然可以用于:

fn main() { let n = Number { odd: true, value: 51 }; let m = n.clone(); let o = n.clone();}

fn main() { let n = Number { odd: true, value: 51 }; let m = n; // `m` is a copy of `n` let o = n; // same. `n` is neither moved nor borrowed.}

有一些traits很常见,它们可以通过使用derive 属性自动实现:

#[derive(Clone, Copy)]struct Number { odd: bool, value: i32,}// this expands to `impl Clone for Number` and `impl Copy for Number` blocks.

看上去,整篇教程都在使用大量代码解释 Rust 的各种语句与用法。可能我们会感觉博客结构不是太明确,但是实例驱动的代码学习确实更加高效。尤其是对于那些有一些编程基础的同学,他们可以快速抓住 Rust 语言的特点与逻辑。

最后,这篇文章并没有展示博客所有的内容,如果读者想真正入门 Rust 语言,推荐可以查阅原博客。

rust大量科技零件_半小时入门Rust,这是一篇Rust代码风暴相关推荐

  1. python爬虫爬取图片无法打开_半小时入门python爬虫爬下网站图片,不能再简单了...

    阅读本文大约需要2分钟 本文旨在为没有爬虫基础的,偶尔想爬虫爬一下某个网站上的图片的初学者使用,如果你已经是对爬虫很熟悉了,下面依然有你感兴趣的东西. 我最近也才正儿八经学习了下爬虫,出于某种需要爬一 ...

  2. rust大量科技零件_并完美解决了业务瓶颈,Rust不是垃圾收集的语言

    概述 案例涉及的是一个企业的业务监控,该用来以帮助人员监控业务API.当客户的应用程序调用API时,会向发送日志,对发送的日志中进行监控和分析. 数据流为平均每分钟处理30k 的API调用.每个客户都 ...

  3. 半小时入门MATLAB编程入门基础知识:

    https://learnxinyminutes.com/docs/zh-cn/matlab-cn/ 半小时入门MATLAB编程入门基础知识: % 以百分号作为注释符 %{ 多行注释 可以 这样 表示 ...

  4. 【半小时入门vue】最容易理解的vue入门方式

    半小时入门vue 前言(!important) 学习vue的前提 什么是vue? vue的引入方式 实例化一个对象和创建一个对象 实例化一个vue对象 模板语法 1.插值表达式 2.v-text和v- ...

  5. 半小时拿下Python数据处理之Matplotlib篇

    半小时拿下Python数据处理之Matplotlib篇 matplotlib三种代码风格 pyplot pylab Object Oriented 子图 多图 散点图(scatter) 条形图 (ba ...

  6. 半小时拿下Python数据处理之Seaborn篇

    半小时拿下Python数据处理之Seaborn篇 Seaborn简介 Seaborn样式 matplotlib与seaborn绘图比较 Seaborn 5种主题风格 用despine()移除轴线 临时 ...

  7. java抽奖程序头像_半小时撸一个抽奖程序

    需求总是很紧急,昨天正在开会收到人力需求,有时间做个抽奖吗?(now 下午四点12,年会五点开始.)还没能等我拒绝,人事又补了一句做不出来我们就不抽奖了,我擦瞬间感觉要是搞不出来会被兄弟们捅死的节奏, ...

  8. 土拍熔断意味着什么_半小时3宗地接连熔断 今日长沙土拍关键字:“焱”

    来源:凤凰网房产长沙站 4月27日,注定会在2020年长沙土拍史上画下浓墨重彩的一笔. 这一天,长沙土地市场迎来出让高峰,7宗地块集中出让,包括3宗工业用地和4宗经营性用地.3宗工业用地与往常一样,上 ...

  9. hive+mysql+速度_半小时搞定Hadoop+Mysql+Hive+Python

    1. 说明 搭建过Hadoop集群的小伙伴一定知道,如果不用docker,半小时配好Hadoop+Mysql+Hive(后简称Hive)肯定是胡吹,有了Docker镜像,没有说明文档,配好了也不一定会 ...

最新文章

  1. 3个写进简历的京东AINLP项目实战
  2. jenkins和docker实现自动化构建部署
  3. Unity UGUI —— 无限循环List(转载)
  4. 更改Windows默认收藏夹路径我有三法
  5. A summary of the post “How I explained OOD to my wife
  6. oracle instant client配置(MAC OS)
  7. WindowsXP命令行修改服务启动选项
  8. 实战,实现幂等的8种方案!
  9. [动画工具] Animations
  10. 1018. 锤子剪刀布 (20)
  11. linux 下解决arp病毒攻击时上网问题的最简单的治标办法
  12. 优化理论05----最速下降法、最速下降法思想、python实现
  13. asp.net控件的Hyperlink控件
  14. 考勤打卡记录数据库表结构_中控zktime5.0考勤管理系统数据库表结构
  15. select2参数介绍
  16. Java LDAP统一身份认证
  17. 使用Hbase Shell和Java API分别进行Hbase的增删改查操作
  18. auto-cpufreq安装及配置过程
  19. 数据分析-思维分析逻辑day05
  20. 【其它】Mac配置输入法切换快捷键

热门文章

  1. Axure RP 9官网下载
  2. 「OrthoFinder2」直系同源基因的寻找以及Orthogroup构建
  3. python区间_Python区间库interval快速入门
  4. 计算机培训的教案,教师计算机培训-教案.doc
  5. 最新袋鼠投资学习版源码+带计跑步功能/早起打卡功能
  6. 自考计算机组装与维修试卷,贵州自考计算机组装与维护模拟试卷一
  7. 使用winzip命令行对文件打包压缩
  8. Memtest86+ 5.0.1系统设置
  9. 电脑wifi密码查看器 v1.0
  10. 监控员工离职动态,这套系统让打工人瑟瑟发抖!