如何生成一个由字母和数字组成的随机字符串?

3

这个问题的第一部分可能很常见,已经有足够的代码示例来解释如何生成一个随机的字母数字字符串。我使用的代码段来自这里

use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;

fn main() {
    let rand_string: String = thread_rng()
        .sample_iter(&Alphanumeric)
        .take(30)
        .collect();

    println!("{}", rand_string);
}

这段代码无法编译,(注:我在使用夜间版本):

error[E0277]: a value of type `String` cannot be built from an iterator over elements of type `u8`
 --> src/main.rs:8:10
  |
8 |         .collect();
  |          ^^^^^^^ value of type `String` cannot be built from `std::iter::Iterator<Item=u8>`
  |
  = help: the trait `FromIterator<u8>` is not implemented for `String`

好的,生成的元素是u8类型的。因此我猜这是u8类型的数组或向量:

use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;

fn main() {
    let r = thread_rng()
        .sample_iter(&Alphanumeric)
        .take(30)
        .collect::<Vec<_>>();
    let s = String::from_utf8_lossy(&r);
    println!("{}", s);
}

这段代码已经编译成功并且可以正常运行!

2dCsTqoNUR1f0EzRV60IiuHlaM4TfK

一切都好,除了我想问问有人能解释一下类型方面到底发生了什么,以及如何进行优化。

问题

  1. .sample_iter(&Alphanumeric) 生成的是 u8 而不是 char 吗?
  2. 我该如何避免第二个变量 s ,直接将 u8 解释为一个 utf-8 字符?我猜内存中的表示方式没有任何改变吧?
  3. 这些字符串的长度应该始终为 30,我该如何优化掉 Vec 的堆分配?此外,它们实际上可以是 char[] 而不是 String
2个回答

3

.sample_iter(&Alphanumeric)生成的是u8而不是char?

是的,这在rand v0.8中已更改。您可以在0.7.3的文档中查看

impl Distribution<char> for Alphanumeric

但是在0.8.0的文档中

impl Distribution<u8> for Alphanumeric

如何避免第二个变量`s`,直接将一个`u8`解释为一个utf-8字符?我猜在内存中的表示并不会改变?有几种方法可以做到这一点,最明显的是将每个`u8`强制转换为一个`char`:
let s: String = thread_rng()
    .sample_iter(&Alphanumeric)
    .take(30)
    .map(|x| x as char)
    .collect();

或者,使用 charFrom<u8> 实例

let s: String = thread_rng()
    .sample_iter(&Alphanumeric)
    .take(30)
    .map(char::from)
    .collect();

当然,在这里,因为你知道每个必须是有效的UTF-8,所以你可以使用String::from_utf8_unchecked,它比from_utf8_lossy更快(尽管可能与as char方法速度相同):
let s = unsafe {
    String::from_utf8_unchecked(
        thread_rng()
            .sample_iter(&Alphanumeric)
            .take(30)
            .collect::<Vec<_>>(),
    )
};

如果因为某些原因,您觉得unsafe有问题并且希望保持安全,那么可以使用较慢的String::from_utf8,并unwrapResult以获得恐慌而不是UB(即使代码永远不应该恐慌或UB):
let s = String::from_utf8(
    thread_rng()
        .sample_iter(&Alphanumeric)
        .take(30)
        .collect::<Vec<_>>(),
).unwrap();

这些字符串的长度应始终为30。我如何优化Vec的堆分配?此外,它们实际上可以是char[]而不是字符串。
首先,相信我,你不想使用字符数组。它们很难处理。如果您想要一个栈字符串,请使用u8数组,然后使用std::str::from_utf8或更快的std::str::from_utf8_unchecked函数(仅当您知道将生成有效的utf8时才可用)。
至于如何优化堆分配,请参考this answer。基本上,需要一些技巧/丑陋的方法(例如制作自己的函数,将迭代器收集到30个元素的数组中)。

一旦常量泛型最终稳定下来,就会有一个更加优美的解决方案。


1
我认为你应该提到一个可能更喜欢使用 Vec<u8> 而不是 Vec<char> 的原因:char 总是占用 4 个字节,这意味着对于任何 ASCII 字母数字序列,存储字符将浪费三倍的内存。 - Ivan C
我相信const泛型现在已经部分稳定了。现在是否存在更漂亮的解决方案? - Frederik Baetens
1
@FrederikBaetens 目前还没有。如果您感兴趣,可以关注/点赞此Github问题(https://github.com/rust-lang/rust/issues/81615),其中包含相关讨论。 - Aplet123

2

rand::distributions::Alphanumeric文档中的第一个示例显示,如果您想将u8转换为char,则应使用char::from函数进行map

use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;

fn main() {
    let rand_string: String = thread_rng()
        .sample_iter(&Alphanumeric)
        .map(char::from) // map added here
        .take(30)
        .collect();

    println!("{}", rand_string);
}

playground


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接