Skip to content

Trait-based Web API bidnings #3934

@MOZGIII

Description

@MOZGIII

Let's take a WritableStream for example.

If it was a trait, could just implement it for a JsValue, like so: impl WritableStream for JsValue { ... } (with some codegen of course).

Then, if needed, we could very elegantly extend WritableStream with WebTransportSendStream with an explicit trait WebTransportSendStream: WritableStream { ... } - and also implement the WebTransportSendStream for JsValue.


With the given WebIDL:

WebIDL

[Exposed=*, Transferable]
interface WritableStream {
  constructor(optional object underlyingSink, optional QueuingStrategy strategy = {});

  readonly attribute boolean locked;

  Promise<undefined> abort(optional any reason);
  Promise<undefined> close();
  WritableStreamDefaultWriter getWriter();
};
[Exposed=(Window,Worker), SecureContext, Transferable]
interface WebTransportSendStream : WritableStream {
  attribute WebTransportSendGroup? sendGroup;
  attribute long long sendOrder;
  Promise<WebTransportSendStreamStats> getStats();
  WebTransportWriter getWriter();
};

The traits would look somewhat like this:

Traits

trait WritableStream: Sized + JsAny {
    fn new() -> Self;
    fn new_with_underlying_sink(underlying_sink: &impl JsObject) -> Self;
    // ...

    fn locked(&self) -> bool;

    fn abort(&self) -> Promise<()>;
    fn abort_with_reason(&self, reason: &impl JsAny) -> Promise<()>;

    fn close(&self) -> Promise<()>;

    fn get_writer(&self) -> Result<impl WritableStreamDefaultWriter>;
}

trait WebTransportSendStream: WritableStream + JsAny {
    fn send_group(&self) -> Option<impl WebTransportSendGroup>;
    fn set_send_group(&self, value: &impl WebTransportSendGroup) -> Option<impl WebTransportSendGroup>;
    fn unset_send_group(&self);

    fn send_order(&self) -> i64;
    fn set_send_order(&self, value: i64);
    fn unset_send_order(&self);
    
    fn get_stats(&self) -> Promise<impl WebTransportSendStreamStats>;

    fn get_writer(&self) -> Result<impl WebTransportWriter>;
}

and impls like this:

Impls

impl WritableStream for JsValue {
    fn new() -> Self {
        __shim_WritableStream_new()
    }

    fn new_with_underlying_sink(underlying_sink: &impl JsObject) -> Self {
        __shim_WritableStream_new_with_underlying_sink(underlying_sink)
    }

    // ... and so on.
}

impl WebTransportSendStream for JsValue {
    fn send_group(&self) -> Option<impl WebTransportSendGroup> {
        __shim_WebTransportSendStream_send_group()
    }

    // ... and so on.
}


A couple things to note:

  • the idea of having a JsAny type, that would work somewhat like a dyn std::any::Any and enable typechecking of the underlying type of any JsValue:

    let random_value: JsValue = ...;
    let stream: impl WebTransportSendStream = JsAny::as_unchecked<dyn WebTransportSendStream>(random_value); // might also have an `instanceof` and/or custom guard variants.

    UPD: no! actually, what we just need is this: trait JsAny { fn as_js_value(self: Self) -> JsValue }; JsValue already has all the impls, so this effectively allows us to cast impl WebTransportSendStream to JsValue and from there to anything:

    let random_value: JsValue = ...;
    // Works cause of impl WebTransportSendStream for JsValue.
    let stream: impl WebTransportSendStream = random_value;
    // Works cause WebTransportSendStream: WritableStream.
    let stream: impl WritableStream = stream;
    //  Doesn't work, as this type of generalization is not necessarily ture: not every `impl WritableStream` is a `impl WebTransportSendStream`
    let stream: impl WebTransportSendStream = stream;
    //  This works though.
    let stream: impl WebTransportSendStream = JsAny::as_js_value(stream);

    This might look scary - but you would just always use generics or trait object if an issue is encountered here.

    Also, we should consider having something like this: JsValue<dyn WebTransportSendStream> - the JsValue is already effectively a pointer to a value, but this way it can also carry on the information about the associated API.

    Then we'd implement the traits for impl WebTransportSendStream for JsValue<dyn WebTransportSendStream> { ... } to provide the type restrictions...

  • the shims being standalone functions instead of being implementation details of a given type.

Originally posted by @MOZGIII in #3933 (comment)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions