Такая функция, как правило, была бы небезопасна, поскольку внутренние устройства могут быть частными (и, следовательно, иметь ограниченный доступ). Например, предположим, что у нас есть новый тип и для него реализовано Drop
.
struct NewType(String);
impl Drop for NewType {
fn drop(&mut self) {
println!("{}", self.0)
}
}
fn main() {
let x = NewType("abc".to_string());
let y = Some(x);
// this causes a compiler error
// let s = match y {
// Some(s) => s.0,
// None => panic!(),
// };
}
(игровая площадка)
Если ваша функция сработает, вы сможетепереместить внутреннюю строку из нового типа. Затем, когда структура удаляется, она может получить доступ к недопустимой памяти.
Тем не менее, вы можете написать макрос, который реализует что-то в этом направлении. Если вы попытаетесь использовать макрос для чего-то, реализующего Drop
, компилятор будет жаловаться, но в противном случае это должно сработать.
macro_rules! extract_impl {
(struct $struct_name: ident($type_name: ty);) => {
struct $struct_name($type_name);
impl $struct_name {
fn extract(item: Option<Self>) -> $type_name {
match item {
Some(item) => item.0,
None => panic!(), // not sure what you want here
}
}
}
};
}
extract_impl! {
struct Num(i32);
}
impl Num {
fn other_fun(&self) {}
}
fn main() {
let x = Num(5);
println!("{}", Num::extract(Some(x)));
}
(детская площадка)
Наличие блока impl
в выходных данных макроса не вызывает проблем, поскольку вы можете иметь столько блоков impl
для одного типа, сколько вам нужно (в исходном модуле).
ЛучшеAPI будет extract
возвращать опцию, а не какое-то бессмысленное значение или панику. Тогда любая ошибка может быть легко обработана вызывающим абонентом.
macro_rules! extract_impl {
(struct $struct_name: ident($type_name: ty);) => {
struct $struct_name($type_name);
impl $struct_name {
fn extract(item: Option<Self>) -> Option<$type_name> {
item.map(|item| item.0)
}
}
};
}
extract_impl! {
struct Num(i32);
}
impl Num {
fn other_fun(&self) {}
}
fn main() {
let x = Num(5);
println!("{:?}", Num::extract(Some(x)));
}
(детская площадка)