泛型加动态调度
Generics plus dynamic dispatch
考虑这样一种情况,我有一个函数 make_numbers
,它应该创建一串随机数,但我想在运行时(用户输入)决定应该使用哪种随机数生成器。更难的是,我们假设 make_numbers
函数对要生成的数字类型具有通用性。
我用伪代码写了我想实现的东西,我明白为什么这样不行。但是,我不知道在 Rust 中用什么惯用的方式来实现这个目标?
我天真的想法是:
- 使用
Box<Rng>
,但这不起作用,因为 Rng
具有通用函数。
- 在
StdRng
和 XorShiftRng
上使用枚举,但我真的想不出写这个的好方法。
你能给我一些提示,说明这个特定问题的完美解决方案是什么样的吗?
注意:这个问题不是关于具有不同类型的不同匹配武器(解决方案可以是 Box
或枚举,如上所述) - 但如何在这种情况下应用这些解决方案。
extern crate rand;
use rand::{Rng, SeedableRng, StdRng};
use rand::prng::XorShiftRng;
use std::string::String;
use rand::distributions::{Distribution, Standard};
use std::fmt::Display;
// Generic function that should work with any type of random number generator
fn make_numbers<T, R: Rng>(rng: &mut R) -> String
where T: Display, Standard: Distribution<T>
{
let mut s = String::new();
for _i in 0..10 {
s.push_str(format!("_{}", rng.gen::<T>()).as_str());
}
s
}
fn main() {
let use_std = true; // -> assume that this will be determined at runtime (e.g. user input)
// Pseudo code, will not work.
let mut rng = match use_std {
true => StdRng::from_seed(b"thisisadummyseedthisisadummyseed".to_owned()),
false => XorShiftRng::from_seed(b"thisisadummyseed".to_owned())
};
let s = make_numbers::<u8>(&mut rng);
// ... do some complex stuff with s ...
print!("{}", s)
}
error[E0308]: match arms have incompatible types
--> src/main.rs:24:19
|
24 | let mut rng = match use_std {
| ___________________^
25 | | true => StdRng::from_seed(b"thisisadummyseedthisisadummyseed".to_owned()),
26 | | false => XorShiftRng::from_seed(b"thisisadummyseed".to_owned())
| | ------------------------------------------------------ match arm with an incompatible type
27 | | };
| |_____^ expected struct `rand::StdRng`, found struct `rand::XorShiftRng`
|
= note: expected type `rand::StdRng`
found type `rand::XorShiftRng`
我想你明白你的match
武器的类型必须相同。 (否则,请参考suggested duplicate。)
我在您的特定情况下看到的另一个选择是为每个手臂调用 make_numbers
:
fn main() {
let use_std = true;
let s = match use_std {
true => make_numbers::<u8, _>(&mut StdRng::from_seed(b"thisisadummyseedthisisadummyseed".to_owned())),
false => make_numbers::<u8, _>(&mut XorShiftRng::from_seed(b"thisisadummyseed".to_owned()))
};
print!("{}", s)
}
我发现如果您在 make_numbers
中添加了很多附加参数,这可能没有意义。
在这种情况下,我求助于宏:
fn main() {
let use_std = true;
macro_rules! call_make_numbers(($t:ty, $rng:ident, $str:expr) => {
make_numbers::<$t, _>(&mut $rng::from_seed($str.to_owned()))
});
let s = match use_std {
true => call_make_numbers!(u8, StdRng, b"thisisadummyseedthisisadummyseed"),
false => call_make_numbers!(u8, XorShiftRng, b"thisisadummyseed"),
};
print!("{}", s)
}
您自己注意到您不能使用 Box<dyn Rng>
,因为 Rng
特征不是对象安全的。不过,rand
crate 提供了一个解决方案:每个 RNG 的基础都是由特性 RngCore
提供的,它是对象安全的,并且 Box<dyn RngCore>
也实现了 Rng
通过这两个特征实现:
第一个实现确保 Box<dyn RngCore>
本身就是 RngCore
,而第二个实现对所有 RngCore
对象实现 Rng
。实际上,您将能够在 RngCore
特征对象上调用所有 Rng
方法,并且实现会在后台动态分派到所需的 RngCore
方法。
利用这个,你可以使用下面的代码:
let mut rng: Box<dyn RngCore> = if use_std {
Box::new(
StdRng::from_seed(b"thisisadummyseedthisisadummyseed".to_owned())
)
} else {
Box::new(
XorShiftRng::from_seed(b"thisisadummyseed".to_owned())
)
};
let s = make_numbers::<u8, _>(&mut rng);
考虑这样一种情况,我有一个函数 make_numbers
,它应该创建一串随机数,但我想在运行时(用户输入)决定应该使用哪种随机数生成器。更难的是,我们假设 make_numbers
函数对要生成的数字类型具有通用性。
我用伪代码写了我想实现的东西,我明白为什么这样不行。但是,我不知道在 Rust 中用什么惯用的方式来实现这个目标?
我天真的想法是:
- 使用
Box<Rng>
,但这不起作用,因为Rng
具有通用函数。 - 在
StdRng
和XorShiftRng
上使用枚举,但我真的想不出写这个的好方法。
你能给我一些提示,说明这个特定问题的完美解决方案是什么样的吗?
注意:这个问题不是关于具有不同类型的不同匹配武器(解决方案可以是 Box
或枚举,如上所述) - 但如何在这种情况下应用这些解决方案。
extern crate rand;
use rand::{Rng, SeedableRng, StdRng};
use rand::prng::XorShiftRng;
use std::string::String;
use rand::distributions::{Distribution, Standard};
use std::fmt::Display;
// Generic function that should work with any type of random number generator
fn make_numbers<T, R: Rng>(rng: &mut R) -> String
where T: Display, Standard: Distribution<T>
{
let mut s = String::new();
for _i in 0..10 {
s.push_str(format!("_{}", rng.gen::<T>()).as_str());
}
s
}
fn main() {
let use_std = true; // -> assume that this will be determined at runtime (e.g. user input)
// Pseudo code, will not work.
let mut rng = match use_std {
true => StdRng::from_seed(b"thisisadummyseedthisisadummyseed".to_owned()),
false => XorShiftRng::from_seed(b"thisisadummyseed".to_owned())
};
let s = make_numbers::<u8>(&mut rng);
// ... do some complex stuff with s ...
print!("{}", s)
}
error[E0308]: match arms have incompatible types
--> src/main.rs:24:19
|
24 | let mut rng = match use_std {
| ___________________^
25 | | true => StdRng::from_seed(b"thisisadummyseedthisisadummyseed".to_owned()),
26 | | false => XorShiftRng::from_seed(b"thisisadummyseed".to_owned())
| | ------------------------------------------------------ match arm with an incompatible type
27 | | };
| |_____^ expected struct `rand::StdRng`, found struct `rand::XorShiftRng`
|
= note: expected type `rand::StdRng`
found type `rand::XorShiftRng`
我想你明白你的match
武器的类型必须相同。 (否则,请参考suggested duplicate。)
我在您的特定情况下看到的另一个选择是为每个手臂调用 make_numbers
:
fn main() {
let use_std = true;
let s = match use_std {
true => make_numbers::<u8, _>(&mut StdRng::from_seed(b"thisisadummyseedthisisadummyseed".to_owned())),
false => make_numbers::<u8, _>(&mut XorShiftRng::from_seed(b"thisisadummyseed".to_owned()))
};
print!("{}", s)
}
我发现如果您在 make_numbers
中添加了很多附加参数,这可能没有意义。
在这种情况下,我求助于宏:
fn main() {
let use_std = true;
macro_rules! call_make_numbers(($t:ty, $rng:ident, $str:expr) => {
make_numbers::<$t, _>(&mut $rng::from_seed($str.to_owned()))
});
let s = match use_std {
true => call_make_numbers!(u8, StdRng, b"thisisadummyseedthisisadummyseed"),
false => call_make_numbers!(u8, XorShiftRng, b"thisisadummyseed"),
};
print!("{}", s)
}
您自己注意到您不能使用 Box<dyn Rng>
,因为 Rng
特征不是对象安全的。不过,rand
crate 提供了一个解决方案:每个 RNG 的基础都是由特性 RngCore
提供的,它是对象安全的,并且 Box<dyn RngCore>
也实现了 Rng
通过这两个特征实现:
第一个实现确保 Box<dyn RngCore>
本身就是 RngCore
,而第二个实现对所有 RngCore
对象实现 Rng
。实际上,您将能够在 RngCore
特征对象上调用所有 Rng
方法,并且实现会在后台动态分派到所需的 RngCore
方法。
利用这个,你可以使用下面的代码:
let mut rng: Box<dyn RngCore> = if use_std {
Box::new(
StdRng::from_seed(b"thisisadummyseedthisisadummyseed".to_owned())
)
} else {
Box::new(
XorShiftRng::from_seed(b"thisisadummyseed".to_owned())
)
};
let s = make_numbers::<u8, _>(&mut rng);