- 原文地址: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、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。