Дженерики плюс динамическая отправка
Рассмотрим случай, когда у меня есть функция make_numbers
который должен создать строку случайных чисел, но где я хочу решить во время выполнения (пользовательский ввод), какой тип генератора случайных чисел следует использовать. Чтобы сделать это еще сложнее, давайте предположим, что make_numbers
Функция, которая будет общей для типа генерируемых чисел.
Я написал то, что хочу достичь с помощью псевдокода, и я понимаю, почему это не работает. Тем не менее, я не знаю, как может выглядеть идиоматический способ в Rust для достижения этой цели?
Мои наивные идеи были бы:
- использование
Box<Rng>
, но это не работает сRng
имеет общие функции. - Используйте enum over
StdRng
а такжеXorShiftRng
, но я не могу придумать хороший способ написать это.
Можете ли вы дать мне несколько советов о том, как будет выглядеть хорошее решение этой конкретной проблемы?
Примечание: этот вопрос не столько о разных спичечных плечах, имеющих разные типы (решения могут быть Box
или enum, как указано выше) - но как применять эти решения в этом случае.
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`
2 ответа
Вы заметили, что не можете использовать Box<dyn Rng>
так как Rng
черта не является объектно-безопасной. rand
Crate предлагает решение для этого, хотя: основа каждого RNG обеспечивается чертой RngCore
, который является объектно-безопасным, и Box<dyn RngCore>
также реализует Rng
с помощью этих двух реализаций черты:
impl<R: RngCore + ?Sized> RngCore for Box<R>
impl<R: RngCore + ?Sized> Rng for R
Первая реализация гарантирует, что Box<dyn RngCore>
является RngCore
сам, а второй реализует Rng
для всех RngCore
объекты. По сути, вы сможете позвонить всем Rng
методы на RngCore
объекты признаков, и реализация динамически отправляет к требуемому 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);
Я думаю, вы понимаете, что типы ваших match
руки должны быть одинаковыми. (В противном случае, пожалуйста, обратитесь к предложенному дубликату.)
Еще один вариант, который я вижу в вашем конкретном случае, это просто 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)
}