- 原文地址:A Simple Web App in Rust, Part 2a
- 原文做者:Joel's Journal
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:LeopPro
若是你還沒看過這個系列的第一部分,請從這裏開始。html
在第一部分,咱們成功的建立了一個 Rust 工程而且編寫了一個「Hello World」 Web 應用。前端
起初,在這個部分中我想寫一個能夠將日期寫入文件系統的程序。可是在這個過程當中,我和類型檢查鬥爭了很久,因此這個部分主要寫這個。android
上一次還不算太糟。但當我以前作這部分的時候,我記得這是最難的部分。ios
讓咱們從移動已存在的 main.rs
開始,這樣咱們就可使用一個新文件。git
$ pwd
/Users/joel/Projects/simple-log
$ cd src/
$ ls
main.rs
$ mv main.rs web_main.rs
$ touch main.rs
複製代碼
我能夠不借助任何參考獨立寫出「Hello World」麼?github
讓我試試:web
fn main() {
println!("Hello, world");
}
複製代碼
而後:後端
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
Running `target/debug/simple-log`
Hello, world
複製代碼
哦,我想我還記得它。我只是有一點不太確定,我是否須要爲 println!
導入些什麼,如今看來,這是沒必要要的。bash
好了,繼續。上網搜索「Rust 建立文件」,我找到了 std::fs::File
:doc.rust-lang.org/std/fs/stru…。讓咱們來試試一個例子:app
use std::fs::File;
fn main() {
let mut f = try!(File::create("foo.txt"));
}
複製代碼
編譯:
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
<std macros>:5:8: 6:42 error: mismatched types:
expected `()`,
found `core::result::Result<_, _>`
(expected (),
found enum `core::result::Result`) [E0308]
<std macros>:5 return $ crate:: result:: Result:: Err (
<std macros>:6 $ crate:: convert:: From:: from ( err ) ) } } )
<std macros>:1:1: 6:48 note: in expansion of try!
src/main.rs:5:17: 5:46 note: expansion site
error: aborting due to previous error
Could not compile `simple-log`.
複製代碼
當我編寫第一個版本的時候,解決這個錯誤真是花費了我不少時間。我再也不常常活躍於社區,因此這些問題的解決方案多是比較粗糙的。弄清楚這個問題給我留下了深入的印象,因此我立刻就知道答案了。
上面代碼的問題是 try!
宏的展開代碼在出現錯誤的狀況下返回 Err
類型。然而 main
返回單元類型(()
)1,這會致使一個類型錯誤。
我認爲有三點難以理解:
main
的返回值,我能夠清楚的明白 'expected' 和 'found'。try!
是如何影響調用它的函數返回值的。固然,我應該查閱 return
在宏中的定義。然而,當我找到一個在 Rust 文檔 中的評論時,我才明白在這個例子中爲何 try!
不能在 main
中調用。在第三點中,咱們提到了擴展宏。查閱擴展宏是調試這類問題很是有效的方法,這值得咱們深究。
首先,我經過搜索「Rust 擴展宏」查閱到這些代碼:
use std::fs::File;
fn main() {
let mut f = try!(File::create("foo.txt"));
}
複製代碼
……咱們能夠經過這種方式使編譯器輸出宏擴展:
$ rustc src/main.rs --pretty=expanded -Z unstable-options
#![feature(no_std)]
#![no_std]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std as std;
use std::fs::File;
fn main() {
let mut f =
match File::create("foo.txt") {
::std::result::Result::Ok(val) => val,
::std::result::Result::Err(err) => {
return ::std::result::Result::Err(::std::convert::From::from(err))
}
};
}
複製代碼
此次就易於調試了。宏是很是有效的工具,可是同其餘工具同樣,咱們要知道什麼時候、怎樣使用它們。
看看吧,上面輸出中的 return
定義,問題就出在這裏。它嘗試返回一個 Err
結果,可是 main
函數指望獲得一個單元類型。
我要知道如何解決這個類型問題。我嘗試模仿 try!
宏,可是此次只返回單元類型:
use std::fs::File;
fn main() {
match File::create("foo.txt") {
Ok(val) => val,
Err(err) => ()
}
}
複製代碼
運行:
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:5:5: 8:6 error: match arms have incompatible types:
expected `std::fs::File`,
found `()`
(expected struct `std::fs::File`,
found ()) [E0308]
src/main.rs:5 match File::create("foo.txt") {
src/main.rs:6 Ok(val) => val,
src/main.rs:7 Err(err) => ()
src/main.rs:8 }
src/main.rs:7:21: 7:23 note: match arm with an incompatible type
src/main.rs:7 Err(err) => ()
^~
error: aborting due to previous error
Could not compile `simple-log`.
複製代碼
我不知道該如何表述,「這裏不該該作任何事」。我猜 val
必定是 std::fs::File
類型,因此編譯器推斷 match
的全部分支都應該返回它。我能夠令 Ok
分支也不返回任何東西麼?
use std::fs::File;
fn main() {
match File::create("foo.txt") {
Ok(val) => (),
Err(err) => ()
}
}
複製代碼
運行:
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:6:12: 6:15 warning: unused variable: `val`, #[warn(unused_variables)] on by default
src/main.rs:6 Ok(val) => (),
^~~
src/main.rs:7:13: 7:16 warning: unused variable: `err`, #[warn(unused_variables)] on by default
src/main.rs:7 Err(err) => ()
^~~
Running `target/debug/simple-log`
$ ls
Cargo.lock Cargo.toml foo.txt src target
複製代碼
它建立了 foo.txt
!固然,代碼能夠更優雅,可是它如今很不錯。讓咱們試試其餘的方法:
use std::fs::File;
fn main() {
File::create("foo.txt")
}
複製代碼
=>
$ rm foo.txt
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:5:5: 5:28 error: mismatched types:
expected `()`,
found `core::result::Result<std::fs::File, std::io::error::Error>`
(expected (),
found enum `core::result::Result`) [E0308]
src/main.rs:5 File::create("foo.txt")
^~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
複製代碼
我以前看到過這個。它的意思是 main
函數返回了 File::create
的結果。我想,這裏不該該返回任何東西,可是我沒有往這個方向思考。若是我添加一個分號呢?
use std::fs::File;
fn main() {
File::create("foo.txt");
}
複製代碼
=>
$ rm foo.txt
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:5:5: 5:29 warning: unused result which must be used, #[warn(unused_must_use)] on by default
src/main.rs:5 File::create("foo.txt");
^~~~~~~~~~~~~~~~~~~~~~~~
Running `target/debug/simple-log`
$ ls
Cargo.lock Cargo.toml foo.txt src target
複製代碼
好了,咱們成功運行且建立了文件,可是如今有一個「未使用的結果」警告。讓咱們去作點什麼來處理這個結果:
use std::fs::File;
fn main() {
match File::create("foo.txt") {
Ok(val) => println!("File created!"),
Err(err) => println!("Error: could not create file.")
}
}
複製代碼
=>
$ rm foo.txt
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:6:12: 6:15 warning: unused variable: `val`, #[warn(unused_variables)] on by default
src/main.rs:6 Ok(val) => println!("File created!"),
^~~
src/main.rs:7:13: 7:16 warning: unused variable: `err`, #[warn(unused_variables)] on by default
src/main.rs:7 Err(err) => println!("Error: could not create file.")
^~~
Running `target/debug/simple-log`
File created!
複製代碼
如今出現了未使用的變量警告。個人直覺是,咱們可使用省略號或刪除變量名來解決這個問題:
use std::fs::File;
fn main() {
match File::create("foo.txt") {
Ok(..) => println!("File created!"),
Err(..) => println!("Error: could not create file.")
}
}
複製代碼
=>
cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
Running `target/debug/simple-log`
File created!
複製代碼
看,使用省略號可行,那麼若是我刪除省略號會發生什麼?
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:6:12: 6:13 error: nullary enum variants are written with no trailing `( )`
src/main.rs:6 Ok() => println!("File created!"),
^
src/main.rs:7:13: 7:14 error: nullary enum variants are written with no trailing `( )`
src/main.rs:7 Err() => println!("Error: could not create file.")
^
error: aborting due to 2 previous errors
Could not compile `simple-log`.
複製代碼
這和我預想的不同。我猜想「nullary」的意思是空元組,它須要被刪除。若是我把括號刪掉:
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:6:9: 6:11 error: this pattern has 0 fields, but the corresponding variant has 1 field [
E0023]
src/main.rs:6 Ok => println!("File created!"),
^~
src/main.rs:7:9: 7:12 error: this pattern has 0 fields, but the corresponding variant has 1 field [
E0023]
src/main.rs:7 Err => println!("Error: could not create file.")
^~~
error: aborting due to 2 previous errors
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
複製代碼
這很好理解,基本上是我所預想的。個人思惟正在造成!
讓咱們嘗試一些更難的東西。這個怎麼樣:
第一個例子進度尚未過半,咱們繼續:
use std::fs::File;
fn log_something(filename, string) {
let mut f = try!(File::create(filename));
try!(f.write_all(string));
}
fn main() {
match log_something("log.txt", "ITS ALIVE!!!") {
Ok(..) => println!("File created!"),
Err(..) => println!("Error: could not create file.")
}
}
複製代碼
=>
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:3:26: 3:27 error: expected one of `:` or `@`, found `,`
src/main.rs:3 fn log_something(filename, string) {
^
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
$
複製代碼
因此我想函數參數必需要聲明類型:
use std::fs::File;
fn log_something(filename: &'static str, string: &'static str) {
let mut f = try!(File::create(filename));
try!(f.write_all(string));
}
fn main() {
match log_something("log.txt", "ITS ALIVE!!!") {
Ok(..) => println!("File created!"),
Err(..) => println!("Error: could not create file.")
}
}
複製代碼
=>
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
<std macros>:5:8: 6:42 error: mismatched types:
expected `()`,
found `core::result::Result<_, _>`
(expected (),
found enum `core::result::Result`) [E0308]
<std macros>:5 return $ crate:: result:: Result:: Err (
<std macros>:6 $ crate:: convert:: From:: from ( err ) ) } } )
<std macros>:1:1: 6:48 note: in expansion of try!
src/main.rs:4:17: 4:45 note: expansion site
src/main.rs:5:12: 5:29 error: type `std::fs::File` does not implement any method in scope named `wr
ite_all`
src/main.rs:5 try!(f.write_all(string));
^~~~~~~~~~~~~~~~~
<std macros>:1:1: 6:48 note: in expansion of try!
src/main.rs:5:5: 5:31 note: expansion site
src/main.rs:5:12: 5:29 help: methods from traits can only be called if the trait is in scope; the f
ollowing trait is implemented but not in scope, perhaps add a `use` for it:
src/main.rs:5:12: 5:29 help: candidate #1: use `std::io::Write`
<std macros>:5:8: 6:42 error: mismatched types:
expected `()`,
found `core::result::Result<_, _>`
(expected (),
found enum `core::result::Result`) [E0308]
<std macros>:5 return $ crate:: result:: Result:: Err (
<std macros>:6 $ crate:: convert:: From:: from ( err ) ) } } )
<std macros>:1:1: 6:48 note: in expansion of try!
src/main.rs:5:5: 5:31 note: expansion site
src/main.rs:10:9: 10:15 error: mismatched types:
expected `()`,
found `core::result::Result<_, _>`
(expected (),
found enum `core::result::Result`) [E0308]
src/main.rs:10 Ok(..) => println!("File created!"),
^~~~~~
src/main.rs:11:9: 11:16 error: mismatched types:
expected `()`,
found `core::result::Result<_, _>`
(expected (),
found enum `core::result::Result`) [E0308]
src/main.rs:11 Err(..) => println!("Error: could not create file.")
^~~~~~~
error: aborting due to 5 previous errors
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
複製代碼
出現了好多錯誤。咱們看第一個錯誤,我猜 log_something
函數須要規定一個返回值。我嘗試了一些方法,可是我卡住了。求助於搜索引擎!
幾分鐘過去了,我終於找到了答案。我在 GitHub 上找到了一些相關信息,可是它並不有效。我大約嘗試了 50 種不一樣方案,最終得以解決:
use std::io::prelude::*;
use std::fs::File;
fn log_something(filename: &'static str, string: &'static str) -> Result<File,std::io::error::Error> {
let mut f = try!(File::create(filename));
try!(f.write_all(string));
}
fn main() {
match log_something("log.txt", "ITS ALIVE!!!") {
Ok(..) => println!("File created!"),
Err(..) => println!("Error: could not create file.")
}
}
複製代碼
我不知道它能正常工做的緣由。若是我理解的正確,返回值的類型 Result
的參數應該是 File
和 std::io::error::Error
。這到底是什麼意思?對我來講,這兩種類型很奇怪,一種是實際結果(文件),另外一種是 Error
類型。爲何?我想,我修復了剩餘錯誤以後,這還須要再次修復。
如今當我嘗試運行它時,我獲得以下錯誤信息:
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:8:22: 8:28 error: mismatched types:
expected `&[u8]`,
found `&'static str` (expected slice, found str) [E0308] src/main.rs:8 try!(f.write_all(string)); ^~~~~~ <std macros>:1:1: 6:48 note: in expansion of try! src/main.rs:8:5: 8:31 note: expansion site error: aborting due to previous error Could not compile `simple-log`. To learn more, run the command again with --verbose. 複製代碼
我在例子中看到他們在字符串前加了一個 b
Ok,我忽略它只是爲了看看會發生什麼。修復參數:
use std::io::prelude::*;
use std::fs::File;
fn log_something(filename: &'static str, string: &'static [u8; 12]) -> Result<File,std::io::error::Error> {
let mut f = try!(File::create(filename));
try!(f.write_all(string));
}
fn main() {
match log_something("log.txt", "ITS ALIVE!!!") {
Ok(..) => println!("File created!"),
Err(..) => println!("Error: could not create file.")
}
}
複製代碼
=>
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:4:85: 4:106 error: struct `Error` is private
src/main.rs:4 fn log_something(filename: &'static str, string: &'static [u8; 12]) -> Result<File, std::io::error::Error> {
^~~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error
Could not compile `simple-log`.
複製代碼
我知道這將出現問題。花一些時間查閱資料。
Rust 文檔有一章介紹 Result
。看起來我作了一些很是規的操做。我是說,這彷佛是「最好」的方式來處理當前的錯誤,可是我很疑惑。我曾見過這個 unwrap
幾回,這看起來多是我想要的。若是我嘗試 unwrap
,事情可能會有所不一樣:
fn log_something(filename: &'static str, string: &'static [u8; 12]) {
let mut f = File::create(filename).unwrap();
f.write_all(string);
}
fn main() {
log_something("log.txt", b"ITS ALIVE!!!")
}
複製代碼
=>
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:6:5: 6:25 warning: unused result which must be used, #[warn(unused_must_use)] on by def
ault
src/main.rs:6 f.write_all(string);
^~~~~~~~~~~~~~~~~~~~
Running `target/debug/simple-log`
$ ls
Cargo.lock Cargo.toml foo.txt log.txt src target
$ cat log.txt
ITS ALIVE!!!
複製代碼
看,雖然有一個警告,它仍是工做了。但我想,這不是 Rust 的方式,Rust 提倡提早失敗或拋出錯誤。
真正的問題是 try!
和 try!
宏返回古怪 Result
的分支:
return $crate::result::Result::Err($crate::convert::From::from(err))
複製代碼
這意味着不管我傳入什麼都必須在枚舉上實現一個 From::from
特徵。可是我真的不知道特徵或枚舉是如何工做的,並且我認爲整個事情對於我來講都是矯枉過正。
我去查閱 Result
的文檔,看起來我走錯了方向:doc.rust-lang.org/std/result/。這裏的 io::Result
例子彷佛於我作的類似,因此讓我看看我是否能解決問題:
use std::io::prelude::*;
use std::fs::File;
use std::io;
fn log_something(filename: &'static str, string: &'static [u8; 12]) -> io::Result<()> {
let mut f = try!(File::create(filename));
try!(f.write_all(string));
}
fn main() {
match log_something("log.txt", b"ITS ALIVE!!!") {
Ok(..) => println!("File created!"),
Err(..) => println!("Error: could not create file.")
}
}
複製代碼
=>
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:5:1: 8:2 error: not all control paths return a value [E0269]
src/main.rs:5 fn log_something(filename: &'static str, string: &'static [u8; 12]) -> io::Result<()>
{
src/main.rs:6 let mut f = try!(File::create(filename));
src/main.rs:7 try!(f.write_all(string));
src/main.rs:8 }
error: aborting due to previous error
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
複製代碼
通過一段時間的思考,我發現了問題:必須在 log_something
的最後添加 OK(())
語句。我經過參考 Result
文檔得出這樣的結論。
我已經習慣了在函數最後的無分號語句意思是 return ()
;而錯誤消息「不是全部的分支都返回同一類型」很差理解 —— 對我來講,這是類型不匹配問題。固然,()
可能不是一個值,我仍然認爲這是使人困惑的。
咱們最終的結果(這篇文章):
use std::io::prelude::*;
use std::fs::File;
use std::io;
fn log_something(filename: &'static str, string: &'static [u8; 12]) -> io::Result<()> {
let mut f = try!(File::create(filename));
try!(f.write_all(string));
Ok(())
}
fn main() {
match log_something("log.txt", b"ITS ALIVE!!!") {
Ok(..) => println!("File created!"),
Err(..) => println!("Error: could not create file.")
}
}
複製代碼
=>
$ rm log.txt
$ cargo run
Running `target/debug/simple-log`
File created!
$ cat log.txt
ITS ALIVE!!!
複製代碼
好了,它工做了,美滋滋!我想在這裏結束本章,由於本章內容很是富有挑戰性。我確信這個代碼是能夠作出改進的,但這裏暫告一段落,下一章咱們將研究 Rust 中的日期和時間.
—
系列文章:使用 Rust 開發一個簡單的 Web 應用
1 哈哈哈哈。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。