03|初窥门径:从你的第一个Rust程序开始!
该思维导图由 AI 生成,仅供参考
- 深入了解
- 翻译
- 解释
- 总结
本文以实际代码示例为主线,通过动手编写程序来快速了解Rust编程语言的基本特点和基础知识。作者介绍了如何从头开始学习Rust编程语言,通过编写第一个Rust程序来快速入门。读者可以通过在VS Code下安装Rust相关插件,并运行一个实用的Rust程序,通过HTTP请求Rust官网首页并将HTML转换成Markdown保存,来体会Rust的基本特点。文章还介绍了Rust的数据结构定义、控制流程、模式匹配和错误处理等内容,展示了Rust作为一种现代编程语言的强大特性和灵活应用。此外,作者还概述了Rust开发的基本内容,包括变量、函数、数据结构等。最后,建议读者逐段写下示例代码并运行,以加深对Rust的印象。整体而言,本文通过实际操作和示例代码,为读者提供了全面的学习参考,使其能够快速了解Rust编程语言的基本概念和特点。
《陈天 · Rust 编程第一课》,新⼈⾸单¥68
全部留言(108)
- 最新
- 精选
- 树静风止置顶2022-12-28,这是一条较新的错误处理解决留言。 如果你是在windows环境下cargo run课程中的代码发现出现以下错误: error: linking with `x86_64-w64-mingw32-gcc` failed: exit code: 1 网上解决方案是安装x86_64-pc-windows-msvc,但是你已经成功安装,却依然报错。 原因是除了安装msvc工具链以外,你还需要切换rust当前默认的工具链。 # 显示当前安装的工具链信息 rustup show # 设置当前默认工具链 rustup default stable-x86_64-pc-windows-msvc 这样你就可以正常编译运行了。
编辑回复: 👍 给你置顶了
2022-12-28归属地:北京4 - Roy Liang置顶export RUSTUP_DIST_SERVER=https://mirrors.sjtug.sjtu.edu.cn/rust-static export RUSTUP_UPDATE_ROOT=https://mirrors.sjtug.sjtu.edu.cn/rust-static/rustup rust国内安装必备环境配置
作者回复: 👍 谢谢提醒!我倒没有关注国内需要镜像的问题。
2021-08-27648 - 赵岩松置顶文中的"Rust 没有语句(statement),只有表达式(expression)"表述我认为是错误的, 我猜这里想表达的内容应该类似于《Rust程序设计语言中》的如下语句 "Rust 是一门基于表达式(expression-based)的语言,这是一个需要理解的(不同于其他语言)重要区别" 但是我的观点是:Rust既存在语句也存在表达式 我的依据为书中接下来的内容 "语句(Statements)是执行一些操作但不返回值的指令。表达式(Expressions)计算并产生一个值" 书中还附带了一个简单的例子,在这里我大体描述一下 `let y = 6;`中,6为一个表达式,它计算出的值是 6,`let y = 6;`做为一个整体是一条语句,他并不返回值,所以我们不能在Rust中这样书写`let x = (let y = 6);` 关于这个问题还有来自交流群内"Tai Huei"提供的截图中的文字作为依据 "Statements are instructions that do something, they do not return a value. Expressions evaluate to a value, they return that value" "Rust is an expression-oriented language. This means that most things are expressions, and evaluate to some kind of value. However, there are also statements. -Steve Klabnik(member if the Rust core team)"
作者回复: 对,这里表达有错误。let 的确是一个 statement。见:https://doc.rust-lang.org/reference/statements.html。我想更着重表达的是,很多语言的 while loop,for loop,if else 都是语句,但 Rust 下它们是表达式,它们会返回最后一个表达式的值(即便是 unit)。我忽视了 let/static/const/fn 这样的定义,它们是纯粹的语句。回头我改一下。
2021-08-27460 - pedro我想很多人不会被课后问题所困扰而是被 Copy 和 Clone,初学时我也很纠结,这里贴上某位大佬的总结: Copy 和 Clone 两者的区别和联系有: Copy内部没有方法,Clone内部有两个方法。 Copy trait 是给编译器用的,告诉编译器这个类型默认采用 copy 语义,而不是 move 语义。Clone trait 是给程序员用的,我们必须手动调用clone方法,它才能发挥作用。 Copy trait不是你想实现就实现,它对类型是有要求的,有些类型就不可能 impl Copy。Clone trait 没有什么前提条件,任何类型都可以实现(unsized 类型除外)。 Copy trait规定了这个类型在执行变量绑定、函数参数传递、函数返回等场景下的操作方式。即这个类型在这种场景下,必然执行的是“简单内存拷贝”操作,这是由编译器保证的,程序员无法控制。Clone trait 里面的 clone 方法究竟会执行什么操作,则是取决于程序员自己写的逻辑。一般情况下,clone 方法应该执行一个“深拷贝”操作,但这不是强制的,如果你愿意,也可以在里面启动一个人工智能程序,都是有可能的。 链接:https://zhuanlan.zhihu.com/p/21730929
作者回复: 我会在讲所有权的时候介绍 Copy/Clone,敬请期待。
2021-08-27336 - GengTeng2. 一个模式匹配就行了,还做到了 panic-free: let args = std::env::args().collect::<Vec<String>>(); if let [_path, url, output, ..] = args.as_slice() { println!("url: {}, output: {}", url, output); } else { eprintln!("参数缺失"); }
作者回复: 👍 非常棒
2021-09-03213 - Kerry课后习题需要自己查一点接口资料,结合错误信息来逐步解决编译问题。 问题一: 重复的abc计算代码可以重构为如下函数: ```rust fn next_fib(a: &mut i32, b: &mut i32) { let c = *a + *b; *a = *b; *b = c; } ``` 以for in循环为例,用法如下: ```rust fn fib_for(n: u8) { let (mut a, mut b) = (1, 1); for _i in 2..n { next_fib(&mut a, &mut b); println!("next val is {}", b); } } ``` 问题二: ```rust fn main() -> Result<(), Box<dyn std::error::Error>> { let args: Vec<String> = env::args().collect(); if args.len() < 3 { println!("Usage: url outpath"); return Ok(()) } args.iter().for_each(|arg| { println!("{}", arg); }); let url = &args[1]; let output = &args[2]; println!("Fetching url: {}", url); let body = &reqwest::blocking::get(url)?.text()?; println!("Converting html to markdown..."); let md = html2md::parse_html(body); fs::write(output, md.as_bytes())?; println!("Converted markdown has been saved in {}.", output); Ok(()) } ``` 好歹是跑起来了……装了智能提示真不错,有better impl的建议 :)
作者回复: 赞!
2021-08-2710 - Quincyuse std::fs; use structopt::StructOpt; #[derive(StructOpt, Debug)] #[structopt(name="scrape_url")] struct Opt { #[structopt(help="input url")] pub url: String, #[structopt(help="output file, stdout if not present")] pub output: Option<String>, } fn main() { let opt = Opt::from_args(); let url = opt.url; let output = &opt.output.unwrap_or("rust.md".to_string()); println!("Fetching url: {}", url); let body = reqwest::blocking::get(url).unwrap().text().unwrap(); println!("Converting html to markdown..."); let md = html2md::parse_html(&body); fs::write(output, md.as_bytes()).unwrap(); println!("Converted markdown has been saved in {}.", output); }
作者回复: 使用了 StructOpt,👍 可以考虑为 url 加上: #[structopt(..., parse(try_from_str = parse_url)], 验证一下。
2021-08-27310 - 慢动作字符串字面量为什么有into方法,这中间经历了什么过程?看文档根本不知道这个方法哪里来的,😂。还是有点操之过急,看到不明白就瞎忙活,感觉还是得循序渐进
编辑回复: 没事放轻松,今天先把代码跑起来就行,之后都会慢慢讲到的。之前咱们学习都是先学语法,再写程序。试试课程这个新玩法啊,啥也别说就是写,先直观感受一波,之后再一点一点抽丝剥茧😁
2021-08-278 - 🔥神山 | 雷神山2. 在 scrape_url 的例子里,我们在代码中写死了要获取的 URL 和要输出的文件名,这太不灵活了。你能改进这个代码,从命令行参数中获取用户提供的信息来绑定 URL 和文件名么?类似这样: ```rust use std::env; use std::error; use std::fs; use std::process; const NOT_ENOUGH_ARGS_CODE: i32 = 1; const NOT_GET_HTTP_DATA_CODE: i32 = 2; const CAN_NOT_CONVERT_MD_CODE: i32 = 3; #[derive(Debug)] struct Config { url: String, //网址 output: String, //输出文件名 } impl Config { fn new(url: String, output: String) -> Config { Config { url, output } } fn curl_url(&self) -> Result<String, Box<dyn error::Error>> { println!("Fetching url: {}", self.url); let body = reqwest::blocking::get(&self.url)?.text()?; Ok(body) } fn convert_md_file(&self, body: String) -> Result<(), Box<dyn error::Error>> { println!("Converting html to markdown..."); let md = html2md::parse_html(&body); fs::write(&self.output, md.as_bytes())?; println!("Converted markdown has been saved in {}.", self.output); Ok(()) } } fn parse_args(args: &[String]) -> Result<Config, &str> { if args.len() < 3 { return Err("Not enough arguments!"); } let url = args[1].clone(); let output = args[2].clone(); Ok(Config::new(url, output)) } fn main() { let args: Vec<String> = env::args().collect(); match parse_args(&args) { Err(err) => { println!("Error: {}", err); process::exit(NOT_ENOUGH_ARGS_CODE); } Ok(cfg) => match cfg.curl_url() { Err(err) => { println!("Error: {}", err.to_string()); process::exit(NOT_GET_HTTP_DATA_CODE); } Ok(body) => { let result = cfg.convert_md_file(body); if result.is_err() { println!("Error: Can NOT write data to {}", cfg.output); process::exit(CAN_NOT_CONVERT_MD_CODE); } } }, } } ```
作者回复: 👍 把实现放入 impl Config {} 是个不错的优化。
2021-08-277 - 核桃这里有几个小疑惑. 1.首先把函数作为参数这里,例如a(1,b(2))这样的,那么和先调用b得到结果再填入a中有什么本质区别吗?我不理解的是,分开调用好像也没什么问题,有什么场景下需要函数作为参数这样调用的? 2.派生宏这里#[derive(Debug)],这个例子中实现的是什么功能不太理解? 3.println! 这里的语法多了一个感叹号,很多语言都有println,多了这个感叹号封装多了一些什么吗?看过一些资料也不太理解 多谢了
作者回复: 1. 函数作为参数是说 a, b 为函数,这样调用:a(1, b),把一个函数指针传给 b 作为参数。 2. #[derive(Debug)] 这里会自动生成一些代码,这些代码实现了 Debug trait。 3. println! 是一个宏,用 ! 是把它和普通函数区分开。因为 println 需要支持可变参数,Rust 不支持可变参数,所以这里用宏实现。
2021-09-086