Могу ли я вернуть структуру, которая использует PhantomData из реализации черты, чтобы добавить время жизни к необработанному указателю без загрязнения интерфейса? - PullRequest
0 голосов
/ 17 мая 2018

В этом вопросе кто-то заметил, что вы можете использовать PhantomData, чтобы добавить время жизни, привязанное к необработанному указателю внутри структуры. Я думал, что попробую сделать это на существующем куске кода, над которым я работал.

Вот наша (минимизированная) отправная точка. Это составляет ( Детская площадка ):

extern crate libc;

use libc::{c_void, free, malloc};

trait Trace {}

struct MyTrace {
    #[allow(dead_code)]
    buf: *mut c_void,
}

impl MyTrace {
    fn new() -> Self {
        Self {
            buf: unsafe { malloc(128) },
        }
    }
}

impl Trace for MyTrace {}

impl Drop for MyTrace {
    fn drop(&mut self) {
        unsafe { free(self.buf) };
    }
}

trait Tracer {
    fn start(&mut self);
    fn stop(&mut self) -> Box<Trace>;
}

struct MyTracer {
    trace: Option<MyTrace>,
}

impl MyTracer {
    fn new() -> Self {
        Self { trace: None }
    }
}

impl Tracer for MyTracer {
    fn start(&mut self) {
        self.trace = Some(MyTrace::new());
        // Pretend the buffer is mutated in C here...
    }

    fn stop(&mut self) -> Box<Trace> {
        Box::new(self.trace.take().unwrap())
    }
}

fn main() {
    let mut tracer = MyTracer::new();
    tracer.start();
    let _trace = tracer.stop();
    println!("Hello, world!");
}

Я думаю, что проблема с приведенным выше кодом заключается в том, что я теоретически мог бы переместить указатель buf из MyTrace и использовать if после того, как структура умерла. В этом случае основной буфер будет освобожден из-за реализации Drop.

Используя PhantomData, мы можем гарантировать, что могут быть получены только ссылки на buf, и что время жизни этих ссылок связано с экземплярами MyTrace, откуда они пришли.

Мы можем действовать следующим образом ( детская площадка ):

extern crate libc;

use libc::{c_void, free, malloc};
use std::marker::PhantomData;

trait Trace {}

struct MyTrace<'b> {
    #[allow(dead_code)]
    buf: *mut c_void,
    _phantom: PhantomData<&'b c_void>,
}

impl<'b> MyTrace<'b> {
    fn new() -> Self {
        Self {
            buf: unsafe { malloc(128) },
            _phantom: PhantomData,
        }
    }
}

impl<'b> Trace for MyTrace<'b> {}

impl<'b> Drop for MyTrace<'b> {
    fn drop(&mut self) {
        unsafe { free(self.buf) };
    }
}

trait Tracer {
    fn start(&mut self);
    fn stop(&mut self) -> Box<Trace>;
}

struct MyTracer<'b> {
    trace: Option<MyTrace<'b>>,
}

impl<'b> MyTracer<'b> {
    fn new() -> Self {
        Self { trace: None }
    }
}

impl<'b> Tracer for MyTracer<'b> {
    fn start(&mut self) {
        self.trace = Some(MyTrace::new());
        // Pretend the buffer is mutated in C here...
    }

    fn stop(&mut self) -> Box<Trace> {
        Box::new(self.trace.take().unwrap())
    }
}

fn main() {
    let mut tracer = MyTracer::new();
    tracer.start();
    let _trace = tracer.stop();
    println!("Hello, world!");
}

Но это даст ошибку:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:53:36
   |
53 |         Box::new(self.trace.take().unwrap())
   |                                    ^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime 'b as defined on the impl at 46:1...
  --> src/main.rs:46:1
   |
46 | impl<'b> Tracer for MyTracer<'b> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: ...so that the types are compatible:
           expected std::option::Option<MyTrace<'_>>
              found std::option::Option<MyTrace<'b>>
   = note: but, the lifetime must be valid for the static lifetime...
   = note: ...so that the expression is assignable:
           expected std::boxed::Box<Trace + 'static>
              found std::boxed::Box<Trace>

У меня есть три подвопроса:

  • Правильно ли я понял мотивацию для PhantomData в этом сценарии?
  • Откуда 'static приходит в сообщении об ошибке?
  • Можно ли заставить это работать без изменения интерфейса stop? В частности, без добавления времени жизни к типу возвращаемого значения?

1 Ответ

0 голосов
/ 17 мая 2018

Я собираюсь проигнорировать ваш прямой вопрос, потому что я полагаю, что вы пришли к нему после неправильного понимания нескольких начальных шагов.

Я мог бы в теории переместить указатель buf из MyTrace и использовать if после того, как структура умерла

Скопируйте указатель, не двигайте его, но да.

Используя PhantomData, мы можем обеспечить получение только ссылок на buf

Это не правда. По-прежнему одинаково легко получить копию необработанного указателя и использовать его неправильно, даже если вы добавите PhantomData.

Правильно ли я понял мотивацию для PhantomData в этом сценарии?

Нет. PhantomData используется, когда вы хотите действовать так, как будто у вас есть значение какого-то типа, но на самом деле его нет. Притворяться, что у вас есть ссылка на что-либо, полезно только тогда, когда есть что-то, что имеет ссылку на . Там нет такого значения для ссылки в вашем примере.

Документы Rust говорят что-то о необработанных указателях и PhantomData, но, возможно, я ошибся

Этот пример действительно хорошо показывает мою точку зрения. Тип Slice должен вести себя так, как если бы он имел ссылку на Vec, у которого он заимствован:

fn borrow_vec<'a, T>(vec: &'a Vec<T>) -> Slice<'a, T>

Поскольку этот тип Slice на самом деле не имеет ссылки, ему нужно от PhantomData до act , как у него есть ссылка. Обратите внимание, что срок службы 'a не просто состоит из цельной ткани - он связан с существующим значением (Vec). Это может привести к небезопасной памяти для Slice после перемещения Vec, поэтому имеет смысл включить срок жизни Vec.

почему комментатор в другом вопросе предложил использовать PhantomData для повышения безопасности типов моего необработанного указателя

Вы можете использовать PhantomData для повышения безопасности необработанных указателей, которые действуют как ссылки, но у вас нет некоторого существующего значения Rust для ссылки. Вы также можете использовать его для корректности, если ваш указатель владеет некоторым значением за ссылкой, что, по-видимому, делает ваше. Однако, поскольку это c_void, это не очень полезно. Обычно вы видите это как PhantomData<MyOwnedType>.

Откуда в сообщении об ошибке появляется 'static?

Зачем нужно добавлять время жизни к черте с помощью оператора плюс (Iterator + 'a)?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...