malwrar
malwrar

Reputation: 33

How can I safely work with external data represented by a unique ID?

A lot of external systems I might want to interact with within Rust (Networked APIs, FFI to legacy C libraries, etc) use numeric IDs to represent remote resources. These IDs typically have an implied context in which they're valid and can expire while a program is holding them.

I'm used to dealing with this in other programming languages, but after reading the Rust book (particularly this chapter, where string indexes are discussed), I feel like it would be clearer and safer to avoid directly exposing these IDs in my program as they are context-dependent and have no guarantees that they will be valid in the future. Are there any patterns in Rust that allow me to safely interact with remote resources represented by an ID?

Consider a simplified example of the specific ABI I'm trying to use that demonstrates the issue:

// Windows are represented as a unique ID per connection.
type WindowId = u64;

/// Fetches a list of Windows from the display server.
fn get_windows() -> Vec<WindowId> {
    // Makes an external call to display server to get a list of Window IDs.
    ...
}

/// Wait for the next event sent by the display server.
fn get_event() -> Event {
    // Blocks until we receive an event, one of which can indicate that a
    // Window has been destroyed and is no longer valid.
    ...
}

/// Example of a function that uses a Window to fetch data.
fn get_window_dimensions(window: WindowId) -> (u32, u32) {
    // Makes an extermal call to display server to ask for the dimensions of
    // the window referenced by the provided WindowId.
   ...
}

/// Example of a function that uses a Window to modify data.
fn set_window_dimensions(window: WindowId, x: u32, y:u32) {
    // Makes an extermal call to display server to set the dimensions for
    // the window referenced by the provided WindowId.
   ...
}

This code requires we refer to Window resources with WindowIds, but notably those IDs could become invalid if:

  1. The Window is destroyed by our program calling an e.g. destroy_window() function.
  2. The Window is destroyed by the remote process/client that owns it exiting.
  3. Our client loses connection, invalidating any WindowIds that might still be stored

Each of these scenarios is considered normal behavior in my case and is expected to be handled by clients. This makes a WindowId feel brittle to use directly without some method of explicitly encoding scope into it. For example, other parts of the program storing a WindowId could suddenly find that the associated Window referenced is invalid, and would somehow need to detect and handle this case.

This feels like could get real messy real quickly! Is there any way I can safely interact with Windows in Rust under these constraints, or am I stuck needing to deal with WindowIds directly?

Upvotes: 3

Views: 173

Answers (1)

Skgland
Skgland

Reputation: 982

Based on your description it appears that a new type idiom struct might be appropriate here, as it appears that the user never needs the actual numeric value of the id.

struct WindowId(u64);

This does not solve the Problem of Ids becoming invalid. To solve Point 1 you could make Functions that cause an Id to become invalid to consume the Id (take it by value), assuming the the new type struct mentioned above does not derive Copy and if reasonable does not implement Clone.

impl WindowId {
    fn destroy(self){
        /* .. snip .. */
    }
}

This still leaves Point 2 and 3, the best option is probably for all operations accessing the window via its ID to be failable e.g. by returning Result.

impl WindowId {
    fn size(&self) -> Result<(u32, u32), WindowError> {
        /* .. snip .. */
    }
}

Upvotes: 1

Related Questions