DelSkayn / rquickjs

High level bindings to the quickjs javascript engine
MIT License
504 stars 63 forks source link

Consider adding Rust delegate wrapper for smooth bindings #131

Open stevefan1999-personal opened 1 year ago

stevefan1999-personal commented 1 year ago

I'm trying to expose Tokio and some std stuff into QJS context. I did it like this:

#[bind(object)]
#[quickjs(bare)]
mod socket {
    use delegate_attr::delegate;
    use derive_more::From;
    use std::net::{IpAddr, SocketAddr};

    #[derive(Clone, Debug, From)]
    pub struct IpAddrWrapper(IpAddr);

    #[delegate(self.0)]
    impl IpAddrWrapper {
        #[quickjs(get, enumerable)]
        pub fn is_unspecified(&self) -> bool;

        #[quickjs(get, enumerable)]
        pub fn is_loopback(&self) -> bool;

        #[quickjs(get, enumerable)]
        pub fn is_multicast(&self) -> bool;

        #[quickjs(get, enumerable)]
        pub fn is_ipv4(&self) -> bool;

        #[quickjs(get, enumerable)]
        pub fn is_ipv6(&self) -> bool;
    }

    #[derive(Clone, Debug, From)]
    pub struct SocketAddrWrapper(SocketAddr);

    #[delegate(self.0)]
    impl SocketAddrWrapper {
        #[quickjs(get, enumerable)]
        pub fn port(&self) -> u16;

        #[quickjs(get, enumerable)]
        pub fn is_ipv4(&self) -> bool;

        #[quickjs(get, enumerable)]
        pub fn is_ipv6(&self) -> bool;

        #[quickjs(get, enumerable)]
        #[into]
        pub fn ip(&self) -> IpAddrWrapper;
    }

    pub struct TcpListener {
        // field properties
        listener: tokio::net::TcpListener,
    }

    pub async fn listen(addr: String) -> rquickjs::Result<TcpListener> {
        Ok(TcpListener {
            listener: tokio::net::TcpListener::bind(addr).await?,
        })
    }

    impl TcpListener {
        // instance property getter
        #[quickjs(get, enumerable)]
        pub fn local_addr(&self) -> rquickjs::Result<SocketAddrWrapper> {
            Ok(self.listener.local_addr()?.into())
        }
    }
}

Despite intellij-rust is reporting an error that something something has no body, the code does compile and does expose the properties well. However when you consider that local_addr function you can clearly see its limitation: there is no Result into support, nor does it works with async well. I don't think this is a trivial task, and hand-writing some of the bindings are fine to me, but this would be a huge improvement in quality of life and experience.

Useful projects:

Kobzol/rust-delegate: Rust method delegation with less boilerplate (github.com)

upsuper/delegate-attr: Attribute proc-macro to delegate method to a field (github.com)

hobofan/ambassador: Delegation of trait implementations via procedural macros (github.com)