Я начал изучать Rust и решил реализовать что-то самостоятельно с нуля. Я реализовал ГПСЧ и использую его для генерации случайных паролей.
Дерево проекта:
| .gitignore
| Cargo.lock
| Cargo.toml
|
+---src
| main.rs
| password.rs
| xorshift.rs
|
main.rs :
use std::io;
mod xorshift;
mod password;
fn main() {
let length : u32;
loop{
let mut pass_leng_str = String::new();
println!("Enter the length of wannable password: ");
io::stdin()
.read_line(&mut pass_leng_str)
.expect("Faild to read the line");
let pass_leng_str = pass_leng_str.trim();
match pass_leng_str.parse::<u32>(){
Ok(i) => {
length = i;
break;
},
Err(..) => {
println!("Not a valid integer!");
},
}
}
println!("Enter the characters you want to exclude: ");
let mut exclude = String::new();
io::stdin()
.read_line(&mut exclude)
.expect("Faild to read the line");
let excluded : Vec<char> = exclude.chars().collect();
println!("{}", password::rand_pass(length, excluded));
}
password.rs :
use super::xorshift;
pub static ASCII : [char; 69] = [
'a', 'b', 'c', 'd', 'e',
'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y',
'z',
'A', 'B', 'C', 'D', 'E',
'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y',
'Z',
'1','2','3','4',
'5','6','7','8',
'9','0',
'+','-','_','?','!','$',"https://codereview.stackexchange.com/",
];
pub fn rand_pass(pass_len : u32, excluded : Vec<char>)-> String{
let mut password = String::new();
for _i in 0..pass_len{
let c : char = loop{
let c = ASCII[xorshift::get_rand(69) as usize];
if !excluded.contains(&c){
break c
}
};
password.push(c);
}
password
}
xorshift.rs :
use std::time::UNIX_EPOCH;
use std::time::SystemTime;
pub fn get_rand(len : u128) -> u128{
let now = SystemTime::now();
let from_unix = now.duration_since(UNIX_EPOCH).expect("Congrats on time travel!");
let seed = from_unix.as_nanos();
let x = seed;
let x = x ^ seed << 13;
let x = x ^ x >>17;
let x = x ^ x <<5;
let x = x % len;
x as u128
}
1 ответ
Добро пожаловать в Rust и добро пожаловать в Code Review!
rustfmt
& clippy
Всегда беги cargo fmt
и cargo clippy
первый. cargo fmt
форматирует ваш код в соответствии с официальным Руководство по стилю Rust, иcargo clippy
выявляет типичные ошибки и предоставляет отзывы об улучшении вашего кода (в данном случае нет).
main.rs
Самая большая проблема с main
функция — это его структура. Логично, что работа main
заключается в:
прочитать длину пароля;
прочитать исключенные символы; и
вывести сгенерированный пароль.
Вы потратили так много кода на первый шаг, что его трудно распознать с первого взгляда, не зная о циклах ввода. Для удобства чтения желательно все это извлечь в функцию:
fn input_length() -> usize {
loop {
eprintln!("Enter the length of password: ");
let mut length = String::new();
io::stdin()
.read_line(&mut length)
.expect("cannot read line");
match length.trim().parse() {
Ok(length) => return length,
Err(_) => eprintln!("Error: invalid lengthn"),
}
}
}
Длины обычно представлены usize
в Rust, чтобы избежать приведения типов, вместо u32
.
Рекомендуется определять переменные как можно ближе к их использованию.
Лично я бы изменил все вхождения println!
кeprintln!
ожидайте тот, который действительно печатает пароль, чтобы программу можно было легко использовать в автоматическом сценарии, прочитавstdout
.
Двигаясь дальше, мы видим, что нет необходимости извлекать все символы вexclude
, а String
, в excluded
, а Vec<char>
(название, кстати, можно улучшить). А String
кодирует символы в UTF-8, который имеет переменную длину, поэтому символы ASCII занимают только 1 байт каждый, тогда как последовательность char
s всегда хранит каждый символ как 4 байта. Более того, UTF-8 специально сконструирован так, что для поиска требуется только линейное сканирование и не вычисляются границы символов, поэтому прирост производительности отсутствует.
password.rs
Это не должно быть работой password
модуль, чтобы предоставить постоянный массив, содержащий символы ASCII. Лучшее имя для ASCII
может бытьALLOWED_CHARS
.
Функция rand_pass
часто будет называться password::
префикс, поэтому нет необходимости повторять эту информацию. Я бы назвал это generate
.
Поскольку функция не потребляет excluded
, возьми &[char]
, или лучше, &str
параметр. Видеть Почему не рекомендуется принимать ссылку на String
(&String
), Vec
(&Vec
), или же Box
(&Box
) в качестве аргумента функции?.
Функцию можно переписать более логически простым способом с помощью итераторов:
use std::iter;
pub fn generate(length: usize, excluded: &str) -> String {
iter::repeat_with(generate_char)
.filter(|c| !excluded.contains(&c))
.take(length)
.collect()
}
куда generate_char
это функция Fn() -> char
который генерирует отдельный случайный символ в пароле, опущен для простоты. Намерение теперь яснее.
xorshift.rs
Лучшее название для модуля могло бы быть time_rng
.
use
объявления могут быть сжатыми:
use std::time::{SystemTime, UNIX_EPOCH};
Здесь мне удобнее читать изменяемую переменную:
let mut x = seed;
x ^= (seed << 13);
x ^= (x >> 17);
x ^= (x << 5);
x % len
Лучше не рассчитывать на то, что читатель знает приоритет оператора здесь.