- 原文地址:A Simple Web App in Rust, Part 2b
- 原文做者:Joel's Journal
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:LeopPro
在这个系列文章中,我记录下了,我在尝试使用 Rust 开发一个简单的 Web 应用过程当中得到的经验。html
到目前为止,咱们有:前端
上一篇文章很恶心。此次咱们会探索 Rust 的时间、日期格式,重点是用一个合适的格式记录时间。android
在 crates.io 中搜索“日期”将获得一个名为 chrono 的包。它热度很高,更新频繁,因此这看起来是一个好的候选方案。 从 README 文件来看,它有着很棒的的日期、时间输出功能。ios
第一件事情是在 Cargo.toml
中添加 Chrono 依赖,但在此以前,咱们先把旧的 main.rs
移出,腾出空间用于实验:git
$ ls
Cargo.lock Cargo.toml log.txt src target
$ cd src/
$ ls
main.rs web_main.rs
$ git mv main.rs main_file_writing.rs
$ touch main.rs
$ git add main.rs
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: main.rs
copied: main.rs -> main_file_writing.rs
Untracked files:
(use "git add <file>..." to include in what will be committed)
../log.txt
$ git commit -m 'move file writing out of the way for working with dates'
[master 4cd2b0e] move file writing out of the way for working with dates
2 files changed, 16 deletions(-)
rewrite src/main.rs (100%)
copy src/{main.rs => main_file_writing.rs} (100%)
复制代码
在 Cargo.toml
中添加 Chrono 依赖:github
[package]
name = "simple-log"
version = "0.1.0"
authors = ["Joel McCracken <mccracken.joel@gmail.com>"]
[dependencies]
chrono = "0.2"
[dependencies.nickel]
git = "https://github.com/nickel-org/nickel.rs.git"
复制代码
自述文件接着说:web
And put this in your crate root:
extern crate chrono;
复制代码
我不知道这是什么意思,但我要尝试把它放到 main.rs
顶部,由于它看起来像是 Rust 代码:后端
extern crate chrono;
fn main() { }
复制代码
编译:bash
$ cargo run
Updating registry `https://github.com/rust-lang/crates.io-index`
Downloading num v0.1.25
Downloading rand v0.3.8
Downloading chrono v0.2.14
Compiling rand v0.3.8
Compiling num v0.1.25
Compiling chrono v0.2.14
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
Running `/Users/joel/Projects/simple-log/target/debug/simple-log`
复制代码
好了,它彷佛下载了 Chrono,而且编译成功了、结束了。我想下一步就是尝试使用它。根据自述文件第一个例子,我想这样:服务器
extern crate chrono;
use chrono::*;
fn main() {
let local: DateTime<Local> = Local::now();
println!('{}', local);
}
复制代码
=>
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
main.rs:6:14: 6:16 error: unterminated character constant: '{ main.rs:6 println!('{}', local); ^~ Could not compile `simple-log`. To learn more, run the command again with --verbose. 复制代码
……?我愣了几秒后,我意识到它是告诉我,我应该使用双引号,而不是单引号。这是有道理的,单引号被用于生命周期规范。
从单引号切换到双引号以后:
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
Running `/Users/joel/Projects/simple-log/target/debug/simple-log`
2015-06-05 16:54:47.483088 -04:00
复制代码
……哇偶,这真简单。看起来 println!
能够调用某种接口以打印各类不一样的东西。
这很讽刺。我很轻松就构建一个简单的“Hello World”级 Web 应用而且打印了一个格式良好的时间,但我在写入文件上花费了不少时间。我不知道这意味着什么。尽管 Rust 语言很难用(对我来讲),可是我相信 Rust 社区已经作了许多努力使系统包工做良好。
我认为,下一步咱们应该将这个字符串写入文件。为此,我想看看上一篇文章的结尾:
$ cat main_file_writing.rs
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.")
}
}
复制代码
我只是将上面那个例子和这个合并到一块儿:
extern crate chrono;
use std::io::prelude::*;
use std::fs::File;
use std::io;
use chrono::*;
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() {
let local: DateTime<Local> = Local::now();
println!('{}', local);
match log_something("log.txt", b"ITS ALIVE!!!") {
Ok(..) => println!("File created!"),
Err(..) => println!("Error: could not create file.")
}
}
复制代码
编译:
$ ls
Cargo.lock Cargo.toml log.txt src target
$ pwd
/Users/joel/Projects/simple-log
$ ls
Cargo.lock Cargo.toml log.txt src target
$ rm log.txt
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
Running `target/debug/simple-log`
2015-06-05 17:08:57.814176 -04:00
File created!
$ cat log.txt
ITS ALIVE!!!$
复制代码
它工做了!和语言做斗争真有意思,很顺利地把两个东西放在一块儿。
咱们离写一个真正的、完整的、最终系统愈来愈近。我忽然想起,我能够为这个代码写一些测试,可是不急,一会再说。
如下是这个函数应该作的事情:
u8
的误解个人第一次尝试:
extern crate chrono;
use std::io::prelude::*;
use std::fs::File;
use std::io;
use chrono::*;
fn log_time(filename: &'static str) -> io::Result<()> { let local: DateTime<Local> = Local::now(); let time_str = local.format("%Y").to_string(); let mut f = try!(File::create(filename)); try!(f.write_all(time_str)); Ok(()) } fn main() { match log_time("log.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)
src/main.rs:13:22: 13:30 error: mismatched types:
expected `&[u8]`,
found `collections::string::String`
(expected &-ptr,
found struct `collections::string::String`) [E0308]
src/main.rs:13 try!(f.write_all(time_str));
^~~~~~~~
<std macros>:1:1: 6:48 note: in expansion of try!
src/main.rs:13:5: 13:33 note: expansion site
error: aborting due to previous error
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
复制代码
我知道 Rust 中有不少字符串类型1,看起来这里我须要另外一种类型。我不知道怎么下手,因此我只能搜索一番。
我记得在 Rust 文档的某一部分中特别提到了字符串。查一查,它说,可使用 &
符号实现从 String
到 &str
的转换。我感受这不是咱们须要的,由于它应该是 [u8]
与 &str
2 之间的类型冲突,让咱们试试:
extern crate chrono;
use std::io::prelude::*;
use std::fs::File;
use std::io;
use chrono::*;
fn log_time(filename: &'static str) -> io::Result<()> { let local: DateTime<Local> = Local::now(); let time_str = local.format("%Y").to_string(); let mut f = try!(File::create(filename)); try!(f.write_all(&time_str)); Ok(()) } fn main() { match log_time("log.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)
src/main.rs:13:22: 13:31 error: mismatched types:
expected `&[u8]`,
found `&collections::string::String`
(expected slice,
found struct `collections::string::String`) [E0308]
src/main.rs:13 try!(f.write_all(&time_str));
^~~~~~~~~
<std macros>:1:1: 6:48 note: in expansion of try!
src/main.rs:13:5: 13:34 note: expansion site
error: aborting due to previous error
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
复制代码
好吧,显然,添加 &
符号只能从 String
转换到 &String
。这彷佛与 Rust 文档中所说的直相矛盾,但也多是我不知道发生了什么事情。
……并且我刚刚读了字符串的章节的末尾。据我所知,这里没有任何东西。
我离开了一段时间去忙别的事情(家长里短,你懂),当我走的时候,我恍然大悟。在此以前,我一直觉得 u8
是 UTF-8
的缩写,可是如今我仔细想一想,它确定是“无符号 8 位整数”的意思。并且我记得我看见过 as_bytes
方法,因此,咱们试一下:
extern crate chrono;
use std::io::prelude::*;
use std::fs::File;
use std::io;
use chrono::*;
fn log_time(filename: &'static str) -> io::Result<()> { let local: DateTime<Local> = Local::now(); let bytes = local.format("%Y").to_string().as_bytes(); let mut f = try!(File::create(filename)); try!(f.write_all(bytes)); Ok(()) } fn main() { match log_time("log.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)
main.rs:10:17: 10:47 error: borrowed value does not live long enough
main.rs:10 let bytes = local.format("%Y").to_string().as_bytes();
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.rs:10:59: 14:2 note: reference must be valid for the block suffix following statement 1 at 10:
58...
main.rs:10 let bytes = local.format("%Y").to_string().as_bytes();
main.rs:11 let mut f = try!(File::create(filename));
main.rs:12 try!(f.write_all(bytes));
main.rs:13 Ok(())
main.rs:14 }
main.rs:10:5: 10:59 note: ...but borrowed value is only valid for the statement at 10:4
main.rs:10 let bytes = local.format("%Y").to_string().as_bytes();
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.rs:10:5: 10:59 help: consider using a `let` binding to increase its lifetime
main.rs:10 let bytes = local.format("%Y").to_string().as_bytes();
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
复制代码
好吧,我但愿事情有所进展。这个错误是否意味着我修正了一些东西,而还有一些其余的错误掩盖了这个问题?我是否是遇到了一个全新的问题?
奇怪的是错误信息集中体如今同一行上。我并非很明白,但我以为它是想告诉我,我须要添加一个赋值语句在方法中。咱们试一下:
fn log_time(filename: &'static str) -> io::Result<()> { let local: DateTime<Local> = Local::now(); let formatted = local.format("%Y").to_string(); let bytes = formatted.as_bytes(); let mut f = try!(File::create(filename)); try!(f.write_all(bytes)); Ok(()) } 复制代码
=>
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
Running `target/debug/simple-log`
File created!
$ cat log.txt
2015$
复制代码
太棒了!咱们想要的都在这了。在我继续以前,我想吐槽一下,我有一点失望。没有个人提示,Rust 也应该能够经过上下文推断正确的行为。
测试脚本:
$ ls
Cargo.lock Cargo.toml log.txt src target
$ rm log.txt
$ cargo run
Running `target/debug/simple-log`
File created!
$ cat log.txt
2015$ cargo run
Running `target/debug/simple-log`
File created!
$ cat log.txt
2015$
复制代码
一些问题:
Let's verify #3 by fixing the format. If the time changes between runs, then we will know that's what is happening.
DateTime
中的 format
方法使用标准 strftime 格式公约。理想状况下,我但愿时间看起来像是这样的:
Sat, Jun 6 2015 05:32:00 PM
Sun, Jun 7 2015 08:35:00 AM
复制代码
……等等。这可读性应该是足够的,供我使用。查阅文档后,我想出了这个:
extern crate chrono;
use std::io::prelude::*;
use std::fs::File;
use std::io;
use chrono::*;
fn log_time(filename: &'static str) -> io::Result<()> { let local: DateTime<Local> = Local::now(); let formatted = local.format("%a, %b %d %Y %I:%M:%S %p\n").to_string(); let bytes = formatted.as_bytes(); let mut f = try!(File::create(filename)); try!(f.write_all(bytes)); Ok(()) } fn main() { match log_time("log.txt") { 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
Sun, Jun 07 2015 06:37:21 PM
$ sleep 5; cargo run
Running `target/debug/simple-log`
File created!
$ cat log.txt
Sun, Jun 07 2015 06:37:41 PM
复制代码
显然,程序覆盖我想要的日志项。我记得 File::create
的文档中指出了这里发生的事。因此,为了正确处理文件我须要再次查阅文档。
我进行了一些搜索,基本上找到答案都是可有可无的。随后,我找到了 std::path::Path 的文档,其中有一个 exists
模式。
此时,个人程序中的类型转换变得愈来愈难以管理。我感到紧张,因此继续以前,我要提交一次。
我想把对时间实体字符串的处理逻辑从 log_time
函数中抽取出来,由于时间的建立与格式化显然与文件操做代码不一样。因此,我作了以下尝试:
extern crate chrono;
use std::io::prelude::*;
use std::fs::File;
use std::io;
use chrono::*;
fn log_time_entry() -> String {
let local: DateTime<Local> = Local::now();
let formatted = local.format("%a, %b %d %Y %I:%M:%S %p\n").to_string();
formatted
}
fn log_time(filename: &'static str) -> io::Result<()> { let bytes = log_time_entry().as_bytes(); let mut f = try!(File::create(filename)); try!(f.write_all(bytes)); Ok(()) } fn main() { match log_time("log.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)
src/main.rs:16:17: 16:33 error: borrowed value does not live long enough
src/main.rs:16 let bytes = log_time_entry().as_bytes();
^~~~~~~~~~~~~~~~
src/main.rs:16:45: 20:2 note: reference must be valid for the block suffix following statement 0 at
16:44...
src/main.rs:16 let bytes = log_time_entry().as_bytes();
src/main.rs:17 let mut f = try!(File::create(filename));
src/main.rs:18 try!(f.write_all(bytes));
src/main.rs:19 Ok(())
src/main.rs:20 }
src/main.rs:16:5: 16:45 note: ...but borrowed value is only valid for the statement at 16:4
src/main.rs:16 let bytes = log_time_entry().as_bytes();
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/main.rs:16:5: 16:45 help: consider using a `let` binding to increase its lifetime
src/main.rs:16 let bytes = log_time_entry().as_bytes();
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
复制代码
好吧,这看起来就像我之前遇到的问题。是否是假借或持有要求函数拥有明确的资源引用?这彷佛有一点奇怪。我再次尝试修复它:
extern crate chrono;
use std::io::prelude::*;
use std::fs::File;
use std::io;
use chrono::*;
fn formatted_time_entry() -> String {
let local: DateTime<Local> = Local::now();
let formatted = local.format("%a, %b %d %Y %I:%M:%S %p\n").to_string();
formatted
}
fn log_time(filename: &'static str) -> io::Result<()> { let entry = formatted_time_entry(); let bytes = entry.as_bytes(); let mut f = try!(File::create(filename)); try!(f.write_all(bytes)); Ok(()) } fn main() { match log_time("log.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!
复制代码
因此,看起来添加一个明确的引用解决了问题。无论怎样,这个规则还蛮简单。
下面,我要将文件操做代码抽取至它本身的函数:
extern crate chrono;
use std::io::prelude::*;
use std::fs::File;
use std::io;
use chrono::*;
fn formatted_time_entry() -> String {
let local: DateTime<Local> = Local::now();
let formatted = local.format("%a, %b %d %Y %I:%M:%S %p\n").to_string();
formatted
}
fn record_entry_in_log(filename: &str, bytes: &[u8]) -> io::Result<()> {
let mut f = try!(File::create(filename));
try!(f.write_all(bytes));
Ok(())
}
fn log_time(filename: &'static str) -> io::Result<()> { let entry = formatted_time_entry(); let bytes = entry.as_bytes(); try!(record_entry_in_log(filename, &bytes)); Ok(()) } fn main() { match log_time("log.txt") { Ok(..) => println!("File created!"), Err(..) => println!("Error: could not create file.") } } 复制代码
它正常工做。我犯了一些一开始的错误,但它们很快被纠正了。这里已是修改后的代码。
查阅文档中的 std::fs::File,我注意到文档对 std::fs::OpenOptions 的介绍,这正是我一直在寻找的。这确定比使用 std::path
更好。
个人第一次尝试:
extern crate chrono;
use std::io::prelude::*;
use std::fs::{File,OpenOptions};
use std::io;
use chrono::{DateTime,Local};
fn formatted_time_entry() -> String {
let local: DateTime<Local> = Local::now();
let formatted = local.format("%a, %b %d %Y %I:%M:%S %p\n").to_string();
formatted
}
fn record_entry_in_log(filename: &str, bytes: &[u8]) -> io::Result<()> {
let mut file = try!(OpenOptions::new().
append(true).
create(true).
open(filename));
try!(file.write_all(bytes));
Ok(())
}
fn log_time(filename: &'static str) -> io::Result<()> { let entry = formatted_time_entry(); let bytes = entry.as_bytes(); try!(record_entry_in_log(filename, &bytes)); Ok(()) } fn main() { match log_time("log.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)
src/main.rs:4:15: 4:19 warning: unused import, #[warn(unused_imports)] on by default
src/main.rs:4 use std::fs::{File,OpenOptions};
^~~~
Running `target/debug/simple-log`
Error: could not create file.
复制代码
有趣。其实它成功建立文件了。哦,我注意错误提示是我硬编码到 main
的信息。我认为这样它将工做:
extern crate chrono;
use std::io::prelude::*;
use std::fs::{File,OpenOptions};
use std::io;
use chrono::{DateTime,Local};
fn formatted_time_entry() -> String {
let local: DateTime<Local> = Local::now();
let formatted = local.format("%a, %b %d %Y %I:%M:%S %p\n").to_string();
formatted
}
fn record_entry_in_log(filename: &str, bytes: &[u8]) -> io::Result<()> {
let mut file = try!(OpenOptions::new().
append(true).
create(true).
open(filename));
try!(file.write_all(bytes));
Ok(())
}
fn log_time(filename: &'static str) -> io::Result<()> { let entry = formatted_time_entry(); let bytes = entry.as_bytes(); try!(record_entry_in_log(filename, &bytes)); Ok(()) } fn main() { match log_time("log.txt") { Ok(..) => println!("File created!"), Err(e) => println!("Error: {}", e) } } 复制代码
=>
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:4:15: 4:19 warning: unused import, #[warn(unused_imports)] on by default
src/main.rs:4 use std::fs::{File,OpenOptions};
^~~~
Running `target/debug/simple-log`
Error: Bad file descriptor (os error 9)
复制代码
奇怪。搜索“非法的文件描述”错误信息彷佛代表,被使用的文件描述已经被关闭了。若是我注释掉 file.write_all
调用,将会发生什么呢?
$ rm log.txt
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:3:5: 3:25 warning: unused import, #[warn(unused_imports)] on by default
src/main.rs:3 use std::io::prelude::*;
^~~~~~~~~~~~~~~~~~~~
src/main.rs:4:15: 4:19 warning: unused import, #[warn(unused_imports)] on by default
src/main.rs:4 use std::fs::{File,OpenOptions};
^~~~
src/main.rs:15:40: 15:45 warning: unused variable: `bytes`, #[warn(unused_variables)] on by default
src/main.rs:15 fn record_entry_in_log(filename: &str, bytes: &[u8]) -> io::Result<()> {
^~~~~
src/main.rs:16:9: 16:17 warning: unused variable: `file`, #[warn(unused_variables)] on by default
src/main.rs:16 let mut file = try!(OpenOptions::new().
^~~~~~~~
src/main.rs:16:9: 16:17 warning: variable does not need to be mutable, #[warn(unused_mut)] on by de
fault
src/main.rs:16 let mut file = try!(OpenOptions::new().
^~~~~~~~
Running `target/debug/simple-log`
File created!
$ ls
Cargo.lock Cargo.toml log.txt src target
复制代码
不出所料,有一堆未使用的警告信息,可是无他,文件的确被建立了。
这彷佛有点傻,但我尝试向函数调用链中添加 .write(true)
后,它工做了。语义上 .append(true)
就意味着 .write(true)
,但我想规定上不是这样的。
搞定了这个,它工做了!最终版本:
extern crate chrono;
use std::io::prelude::*;
use std::fs::{File,OpenOptions};
use std::io;
use chrono::{DateTime,Local};
fn formatted_time_entry() -> String {
let local: DateTime<Local> = Local::now();
let formatted = local.format("%a, %b %d %Y %I:%M:%S %p\n").to_string();
formatted
}
fn record_entry_in_log(filename: &str, bytes: &[u8]) -> io::Result<()> {
let mut file = try!(OpenOptions::new().
append(true).
write(true).
create(true).
open(filename));
try!(file.write_all(bytes));
Ok(())
}
fn log_time(filename: &'static str) -> io::Result<()> { let entry = formatted_time_entry(); let bytes = entry.as_bytes(); try!(record_entry_in_log(filename, &bytes)); Ok(()) } fn main() { match log_time("log.txt") { Ok(..) => println!("File created!"), Err(e) => println!("Error: {}", e) } } 复制代码
=>
$ ls
Cargo.lock Cargo.toml src target
$ cargo run
Running `target/debug/simple-log`
File created!
$ cargo run
Running `target/debug/simple-log`
File created!
$ cat log.txt
Sun, Jun 07 2015 10:40:01 PM
Sun, Jun 07 2015 10:40:05 PM
复制代码
Rust 对我来讲愈来愈容易了。我如今有一些有效的、单功能的代码可使用,我对下一部分程序的开发感到至关有信心。
当我首次规划这个系列的时候,我计划下一个任务是整合日志代码和 nickel.rs
代码,可是如今,我认为这是很是简单的。我猜想,下一个有挑战的部分将是处理选项解析。
—
系列文章:使用 Rust 开发一个简单的 Web 应用
1 有不少种类的字符串是很是合理的事情。字符串是一个复杂的实体,很可贵到正确的表达。不幸的是,乍一看字符串很是简单,这种事情彷佛不必复杂。
2 我也不知道我在说什么。这些就是如今所能企及的。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。