Генератор псевдослучайных чисел и генератор паролей

Я начал изучать 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 ответ
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 байт каждый, тогда как последовательность chars всегда хранит каждый символ как 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

Лучше не рассчитывать на то, что читатель знает приоритет оператора здесь.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *