2015-01-11 15 views
6

Tôi đang sử dụng FFI để viết một số mã Rust chống lại một API C với khái niệm sở hữu mạnh mẽ (API libnotmuch, nếu điều đó quan trọng).Làm thế nào tôi có thể hạn chế tuổi thọ của một cấu trúc theo cấu trúc 'cha'?

Điểm vào chính của API là Cơ sở dữ liệu; Tôi có thể tạo các đối tượng Query từ Database. Nó cung cấp các hàm hủy cho các cơ sở dữ liệu và các truy vấn (và rất nhiều các đối tượng khác).

Tuy nhiên, truy vấn không thể sống lâu hơn Cơ sở dữ liệu mà từ đó nó được tạo. Hàm hủy cơ sở dữ liệu sẽ hủy mọi truy vấn không bị hỏng, vv, sau đó các trình hủy truy vấn không hoạt động.

Cho đến nay, tôi có các phần cơ bản hoạt động - Tôi có thể tạo cơ sở dữ liệu và truy vấn và thực hiện các thao tác trên chúng. Nhưng tôi gặp khó khăn trong việc mã hóa giới hạn suốt đời.

Tôi đang cố gắng để làm một cái gì đó như thế này:

struct Db<'a>(...) // newtype wrapping an opaque DB pointer 
struct Query<'a>(...) // newtype wrapping an opaque query pointer 

Tôi có triển khai của Drop cho mỗi rằng gọi các hàm C destructor cơ bản.

Và sau đó có một chức năng mà tạo ra một truy vấn:

pub fun create_query<?>(db: &Db<?>, query_string: &str) -> Query<?> 

Tôi không biết phải đặt ở vị trí của ? s để truy vấn lại không được phép sống lâu hơn các Db.

Tôi làm cách nào để có thể lập mô hình ràng buộc suốt đời cho API này?

Trả lời

5

Khi bạn muốn gắn kết tuổi thọ của tham số đầu vào với giá trị trả về, bạn cần phải xác định tham số trọn đời trên hàm của bạn và tham chiếu nó trong các kiểu tham số đầu vào và giá trị trả về. Bạn có thể cho bất kỳ tên nào bạn muốn tham số trọn đời này; Thông thường, khi có rất ít thông số, chúng ta chỉ cần đặt tên cho chúng 'a, 'b, 'c vv

loại Db bạn mất một tham số đời, nhưng nó không nên: a Db không tham chiếu tới đối tượng hiện có, vì vậy nó không có ràng buộc suốt đời.

Buộc đúng Db để sống lâu hơn các Query, chúng ta phải viết 'a trên con trỏ vay, chứ không phải trên các tham số đời trên Db mà chúng ta vừa gỡ bỏ.

pub fn create_query<'a>(db: &'a Db, query_string: &str) -> Query<'a> 

Tuy nhiên, điều đó không đủ. Nếu newtypes bạn không tham khảo thông số 'a của họ ở tất cả, bạn sẽ thấy rằng một Query thực sự có thể sống lâu hơn một Db:

struct Db(*mut()); 
struct Query<'a>(*mut()); // ' 

fn create_query<'a>(db: &'a Db, query_string: &str) -> Query<'a> { // ' 
    Query(0 as *mut()) 
} 

fn main() { 
    let query; 
    { 
     let db = Db(0 as *mut()); 
     let q = create_query(&db, ""); 
     query = q; // shouldn't compile! 
    } 
} 

Đó là bởi vì, theo mặc định, các thông số đời là bivariant, tức là trình biên dịch có thể thay thế thông số có thời lượng ngắn hơn là hoặc để đáp ứng các yêu cầu của người gọi.

Khi bạn lưu trữ một con trỏ được mượn trong cấu trúc, tham số suốt đời được coi là contravariant: điều đó có nghĩa là trình biên dịch có thể thay thế tham số với thời lượng ngắn hơn, nhưng không có thời lượng lâu hơn.

Chúng tôi có thể yêu cầu trình biên dịch để điều trị thông số tuổi thọ của bạn như contravariant bằng tay bằng cách thêm một dấu ContravariantLifetime để struct của chúng tôi:

use std::marker::ContravariantLifetime; 

struct Db(*mut()); 
struct Query<'a>(*mut(), ContravariantLifetime<'a>); 

fn create_query<'a>(db: &'a Db, query_string: &str) -> Query<'a> { // ' 
    Query(0 as *mut(), ContravariantLifetime) 
} 

fn main() { 
    let query; 
    { 
     let db = Db(0 as *mut()); 
     let q = create_query(&db, ""); // error: `db` does not live long enough 
     query = q; 
    } 
} 

Bây giờ, trình biên dịch từ chối một cách chính xác sự phân công để query, mà outlives db.


Bonus: Nếu chúng ta thay đổi create_query trở thành một phương pháp Db, chứ không phải là một chức năng miễn phí, chúng ta có thể tận dụng các quy tắc cuộc đời suy luận của trình biên dịch và không viết 'a ở tất cả trên create_query:

use std::marker::ContravariantLifetime; 

struct Db(*mut()); 
struct Query<'a>(*mut(), ContravariantLifetime<'a>); 

impl Db { 
    //fn create_query<'a>(&'a self, query_string: &str) -> Query<'a> 
    fn create_query(&self, query_string: &str) -> Query { 
     Query(0 as *mut(), ContravariantLifetime) 
    } 
} 

fn main() { 
    let query; 
    { 
     let db = Db(0 as *mut()); 
     let q = db.create_query(""); // error: `db` does not live long enough 
     query = q; 
    } 
} 

Khi phương thức có tham số self, trình biên dịch sẽ thích liên kết thời gian tồn tại của tham số đó với kết quả, ngay cả khi có các tham số khác có thời gian tồn tại. Tuy nhiên, đối với các hàm miễn phí, suy luận chỉ có thể xảy ra nếu chỉ có một tham số có tuổi thọ. Ở đây, vì tham số query_string, là loại &'a str, có 2 tham số với toàn bộ thời gian, do đó trình biên dịch không thể suy ra tham số nào chúng tôi muốn liên kết kết quả với.

+0

Cảm ơn! Với thiết kế này, chức năng thử nghiệm mà tôi đã viết với thời gian tồn tại xấu hiện đã bị trình biên dịch từ chối một cách chính xác. –

Các vấn đề liên quan