Есть структура, которая представляет из себя обертку над API.
Я хочу выделить в ней поля, через которые можно будет вызывать различные по функционалу методы.
Например:
api.
account.get_balance(),
api.
inventory.get()
Пытаясь реализовать это, я пришел к следующему коду:
pub struct API<'a> {
api_key: Box<String>,
pub part: APIPart<'a>,
}
impl API<'_> {
fn new(api_key: &str) -> Self {
let api_key = Box::new(api_key.to_owned());
let part = APIPart::new(&*api_key);
return Self {
api_key,
part,
};
}
}
pub struct APIPart<'a> {
api_key: &'a str,
}
impl APIPart<'_> {
fn new(api_key: *const String) -> Self {
let api_key = unsafe { &*api_key };
return Self {
api_key,
};
}
pub fn get_smth(&self) {
println!("Got smth using key: {}", self.api_key);
}
}
Так как APIPart также нужен доступ к api_key, то я сохраняю ссылку на него используя unsafe код, что теоретически должно быть безопасно при условии, что:
- Я могу изменять только внутреннее значение Box(api_key), но не сам Box
- APIPart не живет дольше, чем API(для этого используется лайфтайм 'a)
Проблема в том, что компилятор rust почему то разрешает отделить APIPart от API и использовать его даже при том, что сам API был дропнут:
pub fn foo() {
let api = API::new("123");
let part = api.part;
part.get_smth();
thread::spawn(move || {
loop {
part.get_smth();
sleep(Duration::from_secs(1));
}
});
}
fn main() {
foo();
sleep(Duration::from_secs(3));
}
Я предполагал, что этот код не будет компилироваться, так как здесь получается, что APIPart живет дольше API.
Но он вполне себе компилируется и запускается с выводом:
Got smth using key: 123
Got smth using key:
Got smth using key:
Got smth using key:
что разумеется не то, чего я хочу.
RustPlayground
В общем меня интересует, почему этот код запускается, а также насколько вообще удачна такая архитектура.
Может быть есть варианты получше.