- Оригинальный адрес:A Simple Web App in Rust, Part 4 -- CLI Option Parsing
- Оригинальный автор:Joel's Journal
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:LeopPro
Разработка простого веб-приложения на Rust, часть 4 — Анализ параметров CLI
1 только что вернулся в строй
Привет! Извините за последние два дня. Мы с женой только что купили дом и занимались этим последние два дня. Спасибо за терпеливость.
2 Введение
В предыдущей статье мы построили «работающее» приложение, это доказало, что наш план работает. Чтобы это действительно работало, нам также нужно позаботиться о таких вещах, как параметры командной строки.
Итак, я собираюсь выполнить синтаксический анализ команд. Но сначала давайте удалим существующий код, чтобы мы могли проводить эксперименты по синтаксическому анализу CLI. Но перед этим нам обычно достаточно просто удалить старые файлы, создать новыеmain.rs
:
$ ls
Cargo.lock Cargo.toml log.txt src target
$ cd src/
$ ls
main.rs main_file_writing.rs web_main.rs
main_file_writing.rs
иweb_main.rs
Оба старые файлы, поэтому я их удалил. тогда яmain.rs
переименован вmain_logging_server.rs
, затем создайте новыйmain.rs
.
$ git rm main_file_writing.rs web_main.rs
rm 'src/main_file_writing.rs'
rm 'src/web_main.rs'
$ git commit -m 'remove old files'
[master 771380b] remove old files
2 files changed, 35 deletions(-)
delete mode 100644 src/main_file_writing.rs
delete mode 100644 src/web_main.rs
$ git mv main.rs main_logging_server.rs
$ git commit -m 'move main out of the way for cli parsing experiment'
[master 4d24206] move main out of the way for cli parsing experiment
1 file changed, 0 insertions(+), 0 deletions(-)
rename src/{main.rs => main_logging_server.rs} (100%)
$ touch main.rs
Сосредоточьтесь на анализе параметров. В комментариях к предыдущему посту,Stephan SokolowСпросите меня, рассматривал ли я возможность использования этого пакета для синтаксического анализа командной строки.clap. Хлоп выглядит интересно, так что я собираюсь попробовать.
3 Требования
Следующие службы должны быть настроены по параметрам:
- Расположение файла журнала.
- Закрытый ключ, используемый для аутентификации.
- (Возможно) Установите часовой пояс, используемый для записи времени.
Я только что проверил виртуальную машину Digital Ocean, которую планирую использовать, и это восточное стандартное время, которое является моим часовым поясом, поэтому я, вероятно, пока пропущу третью.
4 Реализация
Насколько я знаю, способ установить зависимость от хлопкаclap = "*";
. Я бы предпочел указать конкретную версию, но пока работает "*".
Мой новый файл Cargo.toml:
[package]
name = "simple-log"
version = "0.1.0"
authors = ["Joel McCracken <mccracken.joel@gmail.com>"]
[dependencies]
chrono = "0.2"
clap = "*"
[dependencies.nickel]
git = "https://github.com/nickel-org/nickel.rs.git"
Установите зависимости:
$ cargo run
Updating registry `https://github.com/rust-lang/crates.io-index`
Downloading ansi_term v0.6.3
Downloading strsim v0.4.0
Downloading clap v1.0.0-beta
Compiling strsim v0.4.0
Compiling ansi_term v0.6.3
Compiling clap v1.0.0-beta
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
error: main function not found
error: aborting due to previous error
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
Эта ошибка только из-за моегоmain.rs
Все еще пусто; все, что имеет значение, это то, что "компиляция хлопка" прошла успешно.
Согласно файлу README, я сначала попробую очень простую версию:
extern crate clap;
use clap::App;
fn main() {
let _ = App::new("fake").version("v1.0-beta").get_matches();
}
бегать:
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
Running `target/debug/simple-log`
$ cargo run
Running `target/debug/simple-log`
$ cargo build --release
Compiling lazy_static v0.1.10
Compiling matches v0.1.2
Compiling bitflags v0.1.1
Compiling httparse v0.1.2
Compiling strsim v0.4.0
Compiling rustc-serialize v0.3.14
Compiling modifier v0.1.0
Compiling libc v0.1.8
Compiling unicase v0.1.0
Compiling groupable v0.2.0
Compiling regex v0.1.30
Compiling traitobject v0.0.3
Compiling pkg-config v0.3.4
Compiling ansi_term v0.6.3
Compiling gcc v0.3.5
Compiling typeable v0.1.1
Compiling unsafe-any v0.4.1
Compiling num_cpus v0.2.5
Compiling rand v0.3.8
Compiling log v0.3.1
Compiling typemap v0.3.2
Compiling clap v1.0.0-beta
Compiling plugin v0.2.6
Compiling mime v0.0.11
Compiling time v0.1.25
Compiling openssl-sys v0.6.2
Compiling openssl v0.6.2
Compiling url v0.2.34
Compiling mustache v0.6.1
Compiling num v0.1.25
Compiling cookie v0.1.20
Compiling hyper v0.4.0
Compiling chrono v0.2.14
Compiling nickel v0.5.0 (https://github.com/nickel-org/nickel.rs.git#69546f58)
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
$ target/debug/simple-log --help
simple-log v1.0-beta
USAGE:
simple-log [FLAGS]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
$ target/release/simple-log --help
simple-log v1.0-beta
USAGE:
simple-log [FLAGS]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
Я не знаю, почему в файле readme мне предлагается использовать--release
компилируется - кажетсяdebug
Работает так же хорошо. И я не знаю, что произойдет. Мы удаляем целевой каталог, не добавляя--release
Скомпилируйте снова:
$ rm -rf target
$ ls
Cargo.lock Cargo.toml log.txt src
$ cargo build
Compiling gcc v0.3.5
Compiling strsim v0.4.0
Compiling typeable v0.1.1
Compiling unicase v0.1.0
Compiling ansi_term v0.6.3
Compiling modifier v0.1.0
Compiling httparse v0.1.2
Compiling regex v0.1.30
Compiling matches v0.1.2
Compiling pkg-config v0.3.4
Compiling lazy_static v0.1.10
Compiling traitobject v0.0.3
Compiling rustc-serialize v0.3.14
Compiling libc v0.1.8
Compiling groupable v0.2.0
Compiling bitflags v0.1.1
Compiling unsafe-any v0.4.1
Compiling clap v1.0.0-beta
Compiling typemap v0.3.2
Compiling rand v0.3.8
Compiling num_cpus v0.2.5
Compiling log v0.3.1
Compiling time v0.1.25
Compiling openssl-sys v0.6.2
Compiling plugin v0.2.6
Compiling mime v0.0.11
Compiling openssl v0.6.2
Compiling url v0.2.34
Compiling num v0.1.25
Compiling mustache v0.6.1
Compiling cookie v0.1.20
Compiling hyper v0.4.0
Compiling chrono v0.2.14
Compiling nickel v0.5.0 (https://github.com/nickel-org/nickel.rs.git#69546f58)
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
$ target/release/simple-log --help
bash: target/release/simple-log: No such file or directory
$ target/debug/simple-log --help
simple-log v1.0-beta
USAGE:
simple-log [FLAGS]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
$
Так что, я думаю, вам не нужно добавлять--release
. Ура, каждый день узнавай что-то новое.
Вернемся и посмотримmain
код, я заметил, что переменная начинается с_
Named; мы предполагаем, что это необходимо для предотвращения предупреждений, устарело. использовать_
Какой отличный стандарт — сказать «преднамеренно не используется», и мне нравится, что Rust поддерживает это.
Что ж, основываясь на ридми хлопка и небольшом эксперименте выше, моя первая попытка написать синтаксический анализатор аргументов:
extern crate clap;
use clap::{App,Arg};
fn main() {
let matches = App::new("simple-log").version("v0.0.1")
.arg(Arg::with_name("LOG FILE")
.short("l")
.long("logfile")
.takes_value(true))
.get_matches();
println!("Logfile path: {}", matches.value_of("LOG FILE").unwrap());
}
=>
$ cargo run -- --logfile whodat
Running `target/debug/simple-log --logfile whodat`
Logfile path: whodat
$ cargo run -- -l whodat
Running `target/debug/simple-log -l whodat`
Logfile path: whodat
Отлично, работает отлично! Но у этого есть проблема:
$ cargo run
Running `target/debug/simple-log`
thread '<main>' panicked at 'called `Option::unwrap()` on a `None` value', /private/tmp/rust2015051
6-38954-h579wb/rustc-1.0.0/src/libcore/option.rs:362
An unknown error occurred
To learn more, run the command again with --verbose.
Кажется, здесь звонятunwrap()
Не очень хорошая идея, потому что параметр не нужно передавать!
Я не знаю, как большое сообщество Rustunwrap
Каково предложение, но я всегда вижу, как сообщество упоминает, почему это должно работать здесь. Тем не менее, я думаю, что это имеет смысл, так как масштаб приложения растет, «приветствуется видеть» сбой в определенной позиции. Ошибка возникает во время выполнения. Это не то, что компилятор может обнаружить!
unwrap
Похожа ли основная идея на исключение нулевого указателя? Я так думаю. Тем не менее, это заставляет вас остановиться и подумать о том, что вы делаете, еслиunwrap
Означает запах кода, что неплохо. Это приводит меня к идее вылить его:
5 Разное
Я твердо верю, что качество кода разработчика нельзя решить на уровне языка. Сообщество статических языков всех мастей — это всегда риторика: «Эти языки удерживают кодеров от плохого кодирования».
Во-первых, вы не можете однозначно определить «хороший код». Действительно, подавляющее большинство того, что делает код великолепным, — это высокая связность. В качестве очень простого примера, спагетти-код имеет тенденцию хорошо работать в прототипе, но спагетти-код ужасного качества.
Ярким примером является недавняя уязвимость OpenSSL. В новостях я не получаю много информации, но источники, которые я собираю, говорят, что уязвимость связана сПлохая бизнес-логикавызванный. В некоторых крайних случаях злоумышленник может выдать себя за CA (доверенную третью сторону). Как вы предотвращаете компиляторомтакойПроблема?
Действительно, это возвращает меня к старой части Чарльза Бэббиджа:
On two occasions I have been asked, "Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?" In one case a member of the Upper, and in the other a member of the Lower, House put this question. I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question.
Лучший способ сделать это — попросить разработчикаПолегчеПрограмма, чтобы сделать правильные вещи рутинными и легкими.
Когда вы думаете, что статическая система типов делает программированиеПолегче, я думаю, что это снова начинает иметь смысл. В конце концов, ответственность за правильность работы программы лежит на разработчике, и мы должны доверять ему и расширять его возможности.
Подводя итог: программисты всегда могут реализовать небольшой интерпретатор Scheme и написать в нем всю логику приложения. Если вы пытаетесь предотвратить что-то подобное с помощью средства проверки типов, удачи.
Хорошо, я закончил, я собираюсь бросить мою болтовню. Спасибо, что высказали мою болтовню.
6 продолжить
Возвращаясь к теме, я заметил, что естьArg
Параметр используется для указания, является ли параметр необязательным. Я чувствую, что мне нужно указать это:
extern crate clap;
use clap::{App,Arg};
fn main() {
let matches = App::new("simple-log").version("v0.0.1")
.arg(Arg::with_name("LOG FILE")
.short("l")
.long("logfile")
.required(true)
.takes_value(true))
.get_matches();
println!("Logfile path: {}", matches.value_of("LOG FILE").unwrap());
}
=>
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
Running `target/debug/simple-log`
error: The following required arguments were not supplied:
'--logfile <LOG FILE>'
USAGE:
simple-log --logfile <LOG FILE>
For more information try --help
An unknown error occurred
To learn more, run the command again with --verbose.
$ cargo run -- -l whodat
Running `target/debug/simple-log -l whodat`
Logfile path: whodat
Это сработало! Следующий вариант, который нам нужен, это указать закрытый ключ через командную строку. Давайте добавим его, но сделаем необязательным, а почему бы и нет? Возможно, мне придется создать общедоступную версию для предварительного просмотра.
Я пишу так:
extern crate clap;
use clap::{App,Arg};
fn main() {
let matches = App::new("simple-log").version("v0.0.1")
.arg(Arg::with_name("LOG FILE")
.short("l")
.long("logfile")
.required(true)
.takes_value(true))
.arg(Arg::with_name("AUTH TOKEN")
.short("t")
.long("token")
.takes_value(true))
.get_matches();
let logfile_path = matches.value_of("LOG FILE").unwrap();
let auth_token = matches.value_of("AUTH TOKEN");
}
=>
$ cargo run -- -l whodat
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:17:9: 17:21 warning: unused variable: `logfile_path`, #[warn(unused_variables)] on by d
efault
src/main.rs:17 let logfile_path = matches.value_of("LOG FILE").unwrap();
^~~~~~~~~~~~
src/main.rs:18:9: 18:19 warning: unused variable: `auth_token`, #[warn(unused_variables)] on by default
src/main.rs:18 let auth_token = matches.value_of("AUTH TOKEN");
^~~~~~~~~~
Running `target/debug/simple-log -l whodat`
У него много (ожидаемых) предупреждений, но он успешно компилируется и запускается. Я просто хотел проверить наличие проблем с типом. Теперь давайте собственно начнем писать программу. Начнем со следующего кода:
use std::io::prelude::*;
use std::fs::OpenOptions;
use std::io;
#[macro_use] extern crate nickel;
use nickel::Nickel;
extern crate chrono;
use chrono::{DateTime,Local};
extern crate clap;
use clap::{App,Arg};
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<String> {
let entry = formatted_time_entry();
{
let bytes = entry.as_bytes();
try!(record_entry_in_log(filename, &bytes));
}
Ok(entry)
}
fn do_log_time(logfile_path: &'static str, auth_token: Option<&str>) -> String {
match log_time(logfile_path) {
Ok(entry) => format!("Entry Logged: {}", entry),
Err(e) => format!("Error: {}", e)
}
}
fn main() {
let matches = App::new("simple-log").version("v0.0.1")
.arg(Arg::with_name("LOG FILE")
.short("l")
.long("logfile")
.required(true)
.takes_value(true))
.arg(Arg::with_name("AUTH TOKEN")
.short("t")
.long("token")
.takes_value(true))
.get_matches();
let logfile_path = matches.value_of("LOG FILE").unwrap();
let auth_token = matches.value_of("AUTH TOKEN");
let mut server = Nickel::new();
server.utilize(router! {
get "**" => |_req, _res| {
do_log_time(logfile_path, auth_token)
}
});
server.listen("127.0.0.1:6767");
}
=>
$ cargo run -- -l whodat
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:60:24: 60:31 error: `matches` does not live long enough
src/main.rs:60 let logfile_path = matches.value_of("LOG FILE").unwrap();
^~~~~~~
note: reference must be valid for the static lifetime...
src/main.rs:58:24: 72:2 note: ...but borrowed value is only valid for the block suffix following st
atement 0 at 58:23
src/main.rs:58 .get_matches();
src/main.rs:59
src/main.rs:60 let logfile_path = matches.value_of("LOG FILE").unwrap();
src/main.rs:61 let auth_token = matches.value_of("AUTH TOKEN");
src/main.rs:62
src/main.rs:63 let mut server = Nickel::new();
...
src/main.rs:61:24: 61:31 error: `matches` does not live long enough
src/main.rs:61 let auth_token = matches.value_of("AUTH TOKEN");
^~~~~~~
note: reference must be valid for the static lifetime...
src/main.rs:58:24: 72:2 note: ...but borrowed value is only valid for the block suffix following st
atement 0 at 58:23
src/main.rs:58 .get_matches();
src/main.rs:59
src/main.rs:60 let logfile_path = matches.value_of("LOG FILE").unwrap();
src/main.rs:61 let auth_token = matches.value_of("AUTH TOKEN");
src/main.rs:62
src/main.rs:63 let mut server = Nickel::new();
...
error: aborting due to 2 previous errors
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
Я не понимаю, что не так - это по сути то же самое, что и пример. Я попытался закомментировать кучу кода, пока он не стал эквивалентен следующему:
fn main() {
let matches = App::new("simple-log").version("v0.0.1")
.arg(Arg::with_name("LOG FILE")
.short("l")
.long("logfile")
.required(true)
.takes_value(true))
.arg(Arg::with_name("AUTH TOKEN")
.short("t")
.long("token")
.takes_value(true))
.get_matches();
let logfile_path = matches.value_of("LOG FILE").unwrap();
let auth_token = matches.value_of("AUTH TOKEN");
}
... и теперь он компилируется. Много предупреждений, но без вреда.
Ни одно из приведенных выше сообщений об ошибках не генерируется закомментированными строками. Теперь, когда я понял, что сообщение об ошибке не обязательно относится к коду, вызывающему проблему, я знаю, что нужно искать в другом месте.
Первое, что я сделал, это удалил ссылку на два параметра. Код становится таким:
fn main() {
let matches = App::new("simple-log").version("v0.0.1")
.arg(Arg::with_name("LOG FILE")
.short("l")
.long("logfile")
.required(true)
.takes_value(true))
.arg(Arg::with_name("AUTH TOKEN")
.short("t")
.long("token")
.takes_value(true))
.get_matches();
let logfile_path = matches.value_of("LOG FILE").unwrap();
let auth_token = matches.value_of("AUTH TOKEN");
let mut server = Nickel::new();
server.utilize(router! {
get "**" => |_req, _res| {
do_log_time("", Some(""))
}
});
server.listen("127.0.0.1:6767");
}
Код компилируется и успешно запускается. Теперь, когда я понимаю проблему, яСомневатьсязапрос GET сопоставляется сget **
Закрытие и передача этих переменных в замыкание вызвали пожизненный конфликт.
Мой друг и яCarol NicholsОбсудила вопрос, и ее советы приблизили меня на шаг к решению проблемы:logfile_path
иauth_token
преобразовать вString
тип.
В чем я могу быть уверен, так это в том, чтоlogfile_path
иauth_token
все дляmatches
где-то в структуре данныхstr
Облик типов, они выходят за рамки в определенное время. существуетmain
конец функции? Так как в конце закрытияmain
Функция все еще работает, кажетсяmatches
все еще существует.
Кроме того, возможно, замыкания не работают с заимствованными переменными. Мне кажется, это маловероятно. Кажется, что компилятор не может быть уверен, когда вызывается замыканиеmatches
все еще будет существовать. Тем не менее, ситуация все еще непонятна, потому что закрытиеserver
среди которых будетmatches
Также конец области!
В любом случае, давайте изменим код следующим образом:
// ...
let logfile_path = matches.value_of("LOG FILE").unwrap();
let auth_token = matches.value_of("AUTH TOKEN");
let mut server = Nickel::new();
server.utilize(router! {
get "**" => |_req, _res| {
do_log_time(logfile_path, auth_token)
}
});
// ...
Измените его на это:
// ...
let logfile_path = matches.value_of("LOG FILE").unwrap().to_string();
let auth_token = match matches.value_of("AUTH TOKEN") {
Some(str) => Some(str.to_string()),
None => None
};
let mut server = Nickel::new();
server.utilize(router! {
get "**" => |_req, _res| {
do_log_time(logfile_path, auth_token)
}
});
server.listen("127.0.0.1:6767");
// ...
…… решил проблему. Я также делаю каждый параметр функции в&str
введите вString
тип.
Конечно, это выявляетновыйпроблема:
$ cargo build
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:69:25: 69:37 error: cannot move out of captured outer variable in an `Fn` closure
src/main.rs:69 do_log_time(logfile_path, auth_token)
^~~~~~~~~~~~
<nickel macros>:1:1: 1:27 note: in expansion of as_block!
<nickel macros>:10:12: 10:42 note: expansion site
note: in expansion of closure expansion
<nickel macros>:9:6: 10:54 note: expansion site
<nickel macros>:1:1: 10:62 note: in expansion of _middleware_inner!
<nickel macros>:4:1: 4:60 note: expansion site
<nickel macros>:1:1: 7:46 note: in expansion of middleware!
<nickel macros>:11:32: 11:78 note: expansion site
<nickel macros>:1:1: 21:78 note: in expansion of _router_inner!
<nickel macros>:4:1: 4:43 note: expansion site
<nickel macros>:1:1: 4:47 note: in expansion of router!
src/main.rs:67:20: 71:6 note: expansion site
src/main.rs:69:39: 69:49 error: cannot move out of captured outer variable in an `Fn` closure
src/main.rs:69 do_log_time(logfile_path, auth_token)
^~~~~~~~~~
<nickel macros>:1:1: 1:27 note: in expansion of as_block!
<nickel macros>:10:12: 10:42 note: expansion site
note: in expansion of closure expansion
<nickel macros>:9:6: 10:54 note: expansion site
<nickel macros>:1:1: 10:62 note: in expansion of _middleware_inner!
<nickel macros>:4:1: 4:60 note: expansion site
<nickel macros>:1:1: 7:46 note: in expansion of middleware!
<nickel macros>:11:32: 11:78 note: expansion site
<nickel macros>:1:1: 21:78 note: in expansion of _router_inner!
<nickel macros>:4:1: 4:43 note: expansion site
<nickel macros>:1:1: 4:47 note: in expansion of router!
src/main.rs:67:20: 71:6 note: expansion site
error: aborting due to 2 previous errors
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
На первый взгляд, я вообще не могу понять эту ошибку:
src/main.rs:69:25: 69:37 error: cannot move out of captured outer variable in an `Fn` closure
src/main.rs:69 do_log_time(logfile_path, auth_token)
Что значит «переместить» захваченную переменную? Я не припоминаю ни одного языка, в котором была бы такая концепция перемещения переменных внутрь и наружу, и это сообщение об ошибке было бы для меня непонятным.
Сообщение об ошибке также сообщило мне некоторые другие странные вещи: что замыкание должно владеть объектами в нем?
Я снова посмотрел в Интернете на это сообщение об ошибке и получил некоторые результаты, но, похоже, у меня ничего не сработало. Итак, идем играть.
7 Больше отладки
Во-первых, я использую--verbose
Скомпилируйте, чтобы увидеть, показывает ли это что-то полезное, но это не печатает никакой дополнительной информации об ошибке, только что-то об общей команде.
Я смутно помнил, что в документации Rust конкретно говорится о замыканиях, поэтому решил проверить. Основываясь на документации, я предполагаю, что мне нужно «переместить» закрытие. но когда я пытаюсь:
server.utilize(router! {
get "**" => move |_req, _res| {
do_log_time(logfile_path, auth_token)
}
});
... выдает новое сообщение об ошибке:
$ cargo run -- -l whodat
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:66:21: 66:25 error: no rules expected the token `move`
src/main.rs:66 get "**" => move |_req, _res| {
^~~~
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
Меня это смутило, поэтому я решил попробовать вынести его наружу:
foo = move |_req, _res| {
do_log_time(logfile_path, auth_token)
};
server.utilize(router! {
get "**" => foo
});
=>
$ cargo run -- -l whodat
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:70:21: 70:24 error: no rules expected the token `foo`
src/main.rs:70 get "**" => foo
^~~
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
Появилось одно и то же сообщение об ошибке.
На этот раз я заметил, что формулировка сообщения об ошибке системы макросов сопоставления с образцом выглядела очень странно, я помнюrouter!
Здесь используются макросы. Некоторые макросы странные! Я знаю, как это исправить, потому что я имел дело с этим раньше.
$ rustc src/main.rs --pretty=expanded -Z unstable-options
src/main.rs:5:14: 5:34 error: can't find crate for `nickel`
src/main.rs:5 #[macro_use] extern crate nickel;
Исходя из этого, я думаю, может быть, мне нужно передать этот параметр грузу. Так? Просматривая грузовую документацию, я не нашел ничего, что могло бы передавать аргументыrustc
Путь.
Немного поискав в Интернете, я обнаружил некоторые проблемы GitHub, в которых говорилось, что передача произвольных параметров не поддерживается, если только не создается пользовательская команда груза, которая, кажется, переходит от проблемы, которую я пытаюсь решить прямо сейчас, к другой ужасной проблеме, поэтому я не т хочу Тогда пойти с этой идеей.
Внезапно мне пришла в голову безумная идея: при использованииcargo run --verbose
Когда я пошел, чтобы увидеть выводrustc
Как выполняется команда:
# ...
Caused by:
Process didn't exit successfully: `rustc src/main.rs --crate-name simple_log --crate-type bin -g -
-out-dir /Users/joel/Projects/simple-log/target/debug --emit=dep-info,link -L dependency=/Users/joel
/Projects/simple-log/target/debug -L dependency=/Users/joel/Projects/simple-log/target/debug/deps --
extern nickel=/Users/joel/Projects/simple-log/target/debug/deps/libnickel-0a4cb77ee6c08a8b.rlib --ex
tern chrono=/Users/joel/Projects/simple-log/target/debug/deps/libchrono-a9b06d7e3a59ae0d.rlib --exte
rn clap=/Users/joel/Projects/simple-log/target/debug/deps/libclap-01156bdabdb6927f.rlib -L native=/U
sers/joel/Projects/simple-log/target/debug/build/openssl-sys-9c1a0f13b3d0a12d/out -L native=/Users/j
oel/Projects/simple-log/target/debug/build/time-30c208bd835b525d/out` (exit code: 101)
# ...
...... Моя хитрость: Могу ли я изменить инструкции по компиляции rustc для вывода кода расширения макроса? Давай попробуем:
$ rustc src/main.rs --crate-name simple_log --crate-type bin -g --out-dir /Users/joel/Projects/simple-log/target/debug --emit=dep-info,link -L dependency=/Users/joel/Projects/simple-log/target/debug -L
dependency=/Users/joel/Projects/simple-log/target/debug/deps --extern nickel=/Users/joel/Projects/simple-log/target/debug/deps/libnickel-0a4cb77ee6c08a8b.rlib --extern chrono=/Users/joel/Projects/simple
-log/target/debug/deps/libchrono-a9b06d7e3a59ae0d.rlib --extern clap=/Users/joel/Projects/simple-log/target/debug/deps/libclap-01156bdabdb6927f.rlib -L native=/Users/joel/Projects/simple-log/target/debu
g/build/openssl-sys-9c1a0f13b3d0a12d/out -L native=/Users/joel/Projects/simple-log/target/debug/build/time-30c208bd835b525d/out --pretty=expanded -Z unstable-options > macro-expanded.rs
$ cat macro-expanded.rs
#![feature(no_std)]
#![no_std]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std as std;
use std::io::prelude::*;
...
Это сработало! Это не очень элегантно, но иногда это работает, по крайней мере, я понял это. Это также дало мне понятьcargo
как позвонитьrustc
из.
Часть вывода, которая работала для нас, была следующей:
server.utilize({
use nickel::HttpRouter;
let mut router = ::nickel::Router::new();
{
router.get("**",{
use nickel::{MiddlewareResult, Responder,
Response, Request};
#[inline(always)]
fn restrict<'a, R: Responder>(r: R, res: Response<'a>)
-> MiddlewareResult<'a> {
res.send(r)
}
#[inline(always)]
fn restrict_closure<F>(f: F) -> F
where F: for<'r, 'b, 'a>Fn(&'r mut Request<'b, 'a, 'b>,
Response<'a>) -> MiddlewareResult<'a> + Send + Sync {
f
}
restrict_closure(
move |_req, _res| {
restrict({
do_log_time(logfile_path, auth_token)
}, _res)
})
});
router
}
});
Ну много информации. Переходим к нижней части кокона.
Есть две функции,restrict
иrestrict_closure
, что меня удивило. ясчитатьОни существуют, чтобы предоставить более точную информацию о типах/ошибках об этих закрытиях обработки запросов.
Однако в этом есть много интересного:
restrict_closure(move |_req, _res| { ... })
... который говорит мне, что макрос указывает, что закрытие является закрытием перемещения. Теоретически это так.
8 Рефакторинг
Давайте проведем рефакторинг и вернемся к проблеме. В этот раз,main
Функция такова:
fn main() {
let matches = App::new("simple-log").version("v0.0.1")
.arg(Arg::with_name("LOG FILE")
.short("l")
.long("logfile")
.required(true)
.takes_value(true))
.arg(Arg::with_name("AUTH TOKEN")
.short("t")
.long("token")
.takes_value(true))
.get_matches();
let logfile_path = matches.value_of("LOG FILE").unwrap().to_string();
let auth_token = match matches.value_of("AUTH TOKEN") {
Some(str) => Some(str.to_string()),
None => None
};
let mut server = Nickel::new();
server.utilize(router! {
get "**" => |_req, _res| {
do_log_time(logfile_path, auth_token)
}
});
server.listen("127.0.0.1:6767");
}
Вывод во время компиляции:
$ cargo build
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:69:25: 69:37 error: cannot move out of captured outer variable in an `Fn` closure
src/main.rs:69 do_log_time(logfile_path, auth_token)
^~~~~~~~~~~~
<nickel macros>:1:1: 1:27 note: in expansion of as_block!
<nickel macros>:10:12: 10:42 note: expansion site
note: in expansion of closure expansion
<nickel macros>:9:6: 10:54 note: expansion site
<nickel macros>:1:1: 10:62 note: in expansion of _middleware_inner!
<nickel macros>:4:1: 4:60 note: expansion site
<nickel macros>:1:1: 7:46 note: in expansion of middleware!
<nickel macros>:11:32: 11:78 note: expansion site
<nickel macros>:1:1: 21:78 note: in expansion of _router_inner!
<nickel macros>:4:1: 4:43 note: expansion site
<nickel macros>:1:1: 4:47 note: in expansion of router!
src/main.rs:67:20: 71:6 note: expansion site
src/main.rs:69:39: 69:49 error: cannot move out of captured outer variable in an `Fn` closure
src/main.rs:69 do_log_time(logfile_path, auth_token)
^~~~~~~~~~
<nickel macros>:1:1: 1:27 note: in expansion of as_block!
<nickel macros>:10:12: 10:42 note: expansion site
note: in expansion of closure expansion
<nickel macros>:9:6: 10:54 note: expansion site
<nickel macros>:1:1: 10:62 note: in expansion of _middleware_inner!
<nickel macros>:4:1: 4:60 note: expansion site
<nickel macros>:1:1: 7:46 note: in expansion of middleware!
<nickel macros>:11:32: 11:78 note: expansion site
<nickel macros>:1:1: 21:78 note: in expansion of _router_inner!
<nickel macros>:4:1: 4:43 note: expansion site
<nickel macros>:1:1: 4:47 note: in expansion of router!
src/main.rs:67:20: 71:6 note: expansion site
error: aborting due to 2 previous errors
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
Я задал этот вопрос в IRC (система обмена мгновенными сообщениями), но не получил ответа. По логике вещей, я должен был потратить немного больше терпения, задавая вопросы в IRC, но нет.
я здесьnickel.rs
В проекте была зарегистрирована проблема, которая, как полагают, вызвана макросами. Это моя последняя мысль - я знаю, что могу ошибаться, но другого пути я не вижу и не хочу сдаваться.
Моя проблема вGithub.com/nickel-org/.... Райман быстро увидел мою ошибку и очень любезно помог мне ее исправить. Очевидно, он прав - если ты можешь это прочитать, Райман, я должен тебе услугу.
Проблема возникает в следующем конкретном замыкании. Давайте проверим, что мы можем найти:
get "**" => |_req, _res| {
do_log_time(logfile_path, auth_token)
}
Вы заметили, здесь, даdo_log_time
Вызов переведенlogfile_path
иauth_token
владение вызывающей функцией. Вот в чем проблема.
Когда я не обучен, я думаю, что это «нормально» и наиболее естественно, как ведет себя код. Я проигнорировал важное предупреждение:В данном случае это лямбда-выражение нельзя вызывать более одного раза.. Когда его вызывают в первый раз,logfile_path
иauth_token
право собственности было переданоdo_log_time
звонивший из . То есть: если эта функция вызывается снова, онане можетПередача владенияdo_log_time
, потому что он больше не содержит обе переменные.
Итак, мы получаем сообщение об ошибке:
src/main.rs:69:39: 69:49 error: cannot move out of captured outer variable in an `Fn` closure
Я все еще не думаю, что это имеет какой-то смысл, но теперь, по крайней мере, я понимаю, что это «выводит» право собственности из-под закрытия.
В любом случае, самый простой способ исправить это:
let mut server = Nickel::new();
server.utilize(router! {
get "**" => |_req, _res| {
do_log_time(logfile_path.clone(), auth_token.clone())
}
});
Теперь при каждом звонкеlogfile_path
иauth_token
Все еще принадлежащие, были созданы клоны, и их право собственности было передано.
Тем не менее, я хотел бы отметить, что я все еще думаю, что это неоптимальное решение. Поскольку процесс передачи права собственности недостаточно прозрачен, теперь я стараюсь использовать ссылки, когда это возможно.
В Rust было бы лучше использовать явную нотацию для представления заимствованной ссылки и другую явную нотацию для представления владения.*
Это работает? Я не знаю, но это действительно интересный вопрос.
9 Рефакторинг
Я попробую быстрый рефакторинг, чтобы посмотреть, смогу ли я использовать ссылки. Это будет интересно, так как у меня могут возникнуть некоторые непредвиденные проблемы - посмотрим!
Я читал книгу Мартина Фаулера по рефакторингу, и она освежила мои ценности, чтобы начать с маленьких шагов. В качестве первого шага я просто хочу преобразовать собственность в обличье; мы начнем сlogfile_path
Начинать:
fn do_log_time(logfile_path: String, auth_token: Option<String>) -> String {
match log_time(logfile_path) {
Ok(entry) => format!("Entry Logged: {}", entry),
Err(e) => format!("Error: {}", e)
}
}
// ...
fn main() {
// ...
server.utilize(router! {
get "**" => |_req, _res| {
do_log_time(logfile_path.clone(), auth_token.clone())
}
});
// ...
}
Измените его на:
fn do_log_time(logfile_path: &String, auth_token: Option<String>) -> String {
match log_time(logfile_path.clone()) {
Ok(entry) => format!("Entry Logged: {}", entry),
Err(e) => format!("Error: {}", e)
}
}
// ...
fn main() {
// ...
server.utilize(router! {
get "**" => |_req, _res| {
do_log_time(&logfile_path, auth_token.clone())
}
});
// ...
}
Этот рефакторинг должен быть реализован:Замена права собственности и клонирования кредитованием. Если у меня есть объект, и я хочу преобразовать его в финт, и я хочу передать его право собственности кому-то еще, я должен сначала создать свою собственную внутреннюю копию. Это позволяет мне превратить мой титул в обличье и при необходимости передать титул. Конечно, это связано с клонированием заимствованного объекта, что дублирует память и увеличивает производительность, но таким образом я могу безопасно изменить эту строку кода. Затем я могу продолжать использовать обличье для замены владельца, ничего не нарушая.
После многократных попыток я получаю следующий код:
use std::io::prelude::*;
use std::fs::OpenOptions;
use std::io;
#[macro_use] extern crate nickel;
use nickel::Nickel;
extern crate chrono;
use chrono::{DateTime,Local};
extern crate clap;
use clap::{App,Arg};
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: &String, 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: &String) -> io::Result<String> {
let entry = formatted_time_entry();
{
let bytes = entry.as_bytes();
try!(record_entry_in_log(filename, &bytes));
}
Ok(entry)
}
fn do_log_time(logfile_path: &String, auth_token: &Option<String>) -> String {
match log_time(logfile_path) {
Ok(entry) => format!("Entry Logged: {}", entry),
Err(e) => format!("Error: {}", e)
}
}
fn main() {
let matches = App::new("simple-log").version("v0.0.1")
.arg(Arg::with_name("LOG FILE")
.short("l")
.long("logfile")
.required(true)
.takes_value(true))
.arg(Arg::with_name("AUTH TOKEN")
.short("t")
.long("token")
.takes_value(true))
.get_matches();
let logfile_path = matches.value_of("LOG FILE").unwrap().to_string();
let auth_token = match matches.value_of("AUTH TOKEN") {
Some(str) => Some(str.to_string()),
None => None
};
let mut server = Nickel::new();
server.utilize(router! {
get "**" => |_req, _res| {
do_log_time(&logfile_path, &auth_token)
}
});
server.listen("127.0.0.1:6767");
}
мне нужно разобраться с этим немедленноauth_token
, но это должно прийти к концу на данный момент.
10 Заключение и обзор части IV
Приложение теперь имеет возможность парсить опции. Однако это очень сложно. Пытаясь решить свою проблему, я чуть не загнался в угол. Я был бы очень расстроен, если бы на мой вопрос на сайте Nickel.rs не было такого полезного ответа.
Некоторые уроки:
- Передача права собственности — дело непростое. Я думаю, для меня новый эмпирический тезис заключается в том, что еслине нужноИспользуйте владение и попробуйте передать параметры через неизменяемые заимствования.
- Cargo действительно долженпредоставить прямой доступ к
rustc
Методы. - Некоторые сообщения об ошибках Rust не очень приятны.
- Даже если сообщение об ошибке плохое, Rust прав — передавать право собственности моему замыканию неправильно, потому что функция вызывается каждый раз, когда запрашивается страница. Вот один из уроков для меня: если я не понимаю сообщение об ошибке, тоНачните с кодаЭто хороший способ подумать о вещах, особенно о том, что идет вразрез с идеей Rust о безопасности памяти.
Этот опыт также укрепил мою терпимость к ошибкам компиляции в строго типизированных языках программирования. Иногда вам действительно нужно понятьвнутреннийчто происходит, чтобы было понятно, что происходит. В этом случае трудно создать минимальную воспроизводимую ошибку, чтобы проиллюстрировать проблему.
Если сообщение об ошибке не содержит необходимой информации, следующий лучший вариант — начать поиск в Интернете информации, связанной с сообщением об ошибке. На самом деле это не поможет вам исследовать, понять и решить проблему самостоятельно.
Я думаю, это можно оптимизировать, добавив некоторые результаты многократных запросов компилятору в разных состояниях, чтобы узнать больше о проблеме. Приятно открывать интерактивную подсказку при ошибках компиляции, но даже комментирование кода для запроса деталей у компилятора может быть очень полезным.
—
Я пишу это примерно через месяц, в основном потому, что слишком занят покупками дома. Иногда я чувствуюОченьРасстроенный. Я думал, что интеграция синтаксического анализа опций была самой простой задачей!
Однако осознание того, что Rust выявил проблему с моей программой, улучшило мое настроение. Даже если сообщение об ошибке не так хорошо, как хотелось бы, мне нравится разумная ошибка сегментации, которая спасает меня от нее.
Я надеюсь, что сообщения об ошибках станут лучше по мере развития Rust. Если я захочу, я думаю, что все мои заботы исчезнут.
—
Серия статей: Разработка простого веб-приложения на Rust
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.