@epitxx

Как поделить api на части с различным функционалом?

Есть структура, которая представляет из себя обертку над 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
В общем меня интересует, почему этот код запускается, а также насколько вообще удачна такая архитектура.
Может быть есть варианты получше.
  • Вопрос задан
  • 135 просмотров
Пригласить эксперта
Ответы на вопрос 1
vabka
@vabka Куратор тега Rust
Лучше это не через поля, а через методы реализовывать - тогда будет более дёшево, да и женерик лишний можно будет убрать.

Что-то типа
struct Api {
  key: String
}
struct ApiPart<'a>{
  api: &'a Api
}

impl Api {
  pub fn part<'a>(&'a self) -> ApiPart<'a> {
    ApiPart {api: self}
  }
}


PS:
Вот так делать точно не нужно:
Не надо на ровном месте городить указатели и unsafe.
У тебя из-за unsafe получился dangling pointer.
fn new(api_key: *const String) -> Self {
        let api_key = unsafe { &*api_key };
        return Self {
            api_key,
        };
    }


Норм практика делать вот так, в случае строк:
fn new(api_key: impl Into<String>) -> Self {
        let api_key = api_key.into();
        return Self {
            api_key,
        };
    }


Боксить строки также не нужно - они и так в куче лежат.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы