The java_package macro

The java_package macro creates Rust structures to interact with a java package -- we call this oxidizing the Java class. Here is an example input to java_package that shows off the various features.

package my.package;

// Oxidize a class, ignoring all its methods or other details. This is useful
// for classes that the Rust code needs to pass around opaquely but doesn't have to
// actually use.
class SimpleClass { }

// Oxidize a class with all details inferred via Java reflection. This will cause
// compilation errors if the class employs Java features that can't be supported
// by duchess in Rust, such as overloaded functions or some of the richer uses
// of Java wildcards (e.g., `ArrayList<Class<?>>`)
//
// Careful: since Java's semver rules are different from Rust's rules,
// this can cause breakage if you update the Java package without updating
// to a new Rust major version. For example, the java package might add a new
// overloaded function; this is not a breaking change in Java, but it will cause
// an error in your Rust crate.
//
// Therefore, we recommend that libraries which wish to maintain a semver guarantee
// avoid this form.
class ReflectedClass { * }

// The preferred form is to specify exactly which parts of the Java API you wish
// to include in the oxidized Rust type. This format is precisely the same as the
// one generated by `javap -public`, so we recommend that you simply run that tool
// and copy-and-paste the output in. You can then remove any methods or other details that
// are causing trouble.
class SpecifiedClass 
    extends some.SuperClass           // Must be some superclass of `SpecifiedClass`
    implements some.Interface1,
               some.Interface2<Type>  // Must be interfaces implemented by `SpecifiedClass` or some superclass
{
    // Mirror a constructor by using the name of the class, along with types
    // for its arguments. Note that we use full types. You can generate these signatures
    // with `javap -public`.
    SpecifiedClass(java.lang.String, java.util.List<String>);

    // Mirror a method with the given signature.
    void methodName(byte[], int);
}

Notes on Java generics and erasure

We do our best to reflect Java generics in Rust, but the two systems are not fully compatible. In particular, Java wildcards (e.g., Class<?>) are only supported in limited scenarios. You may have to remove methods that make use of them.

When you oxidize a class, you can choose to oxidize it in an erased fashion, meaning that you omit all of its generic parameters. This is generally discouraged but sometimes useful.

Generated Rust code

This will generate a Rust module structure containing:

  • a module for each Java package
  • for each oxidized Java class Foo:
    • a struct Foo and a trait FooExt for each oxidized Java class Foo
      • the trait defines methods on Foo that can be invoked on any JVM operation that returns a Foo.
    • impls of the JRef trait for each superclass and interface, to permit upcasting

For the example above we would get

pub mod my {
    pub mod package {
        // References to java types branch to duchess; other references
        // make use of whatever names you have in scope.
        use duchess::java;
        use super::super::*;

        // For `SimpleClass`, we didn't oxidize any methods or supertype,
        // so we get an empty trait and the ability to upcast to `Object`:
        pub struct SimpleClass { /* ... */ }
        pub trait SimpleClassExt { }
        impl JRef<java::lang::Object> for SimpleClass { }

        // For `ReflectedClass`, the methods/upcasts are derived from
        // whatever we found in the Java code.
        pub struct ReflectedClass { /* ... */ }
        pub trait ReflectedClassExt { /* ... */ }
        impl JRef<java::lang::Object> for ReflectedClass { }
        impl JRef</* ... */> for ReflectedClass { }

        // For `SpecifiedClass`, the methods/upcasts are generated from
        // the details we included. Note that `some.SuperClass` and `some.Interface1`
        // refer to the package `some`, which was not part of the `java_package`
        // invocation. Therefore, you must have imported via a `use`
        // statement or the Rust compiler will give errors about an undeclared `some`
        // module.
        pub struct SpecifiedClass { /* ... */ }
        pub trait SpecifiedClassExt { /* ... */ }
        impl JRef<some::SuperClass> for SpecifiedClass { }
        impl JRef<some::Interface1> for SpecifiedClass { }
        impl JRef<some::Interface2<Type>> for SpecifiedClass { }
        impl JRef<java::lang::Object> for SpecifiedClass { }

        // Oxidizing a generic Java class yields a generic Rust struct:
        pub struct MyList<E> { /* ... */ }
        pub trait MyListExt<E> { /* ... */ }
        impl<E> JRef<java::util::AbstractList<E>> for MyList<E> { }
    }
}

Multiple packages

You can (and should) declare multiple packages together:

package foo.bar;

class C1 { }

package foo.baz;

class C2 { }

This allows the macro to generate combined Rust modules:

pub mod foo {
    pub mod bar { 
        pub struct C1 { .. }
        pub trait C1Ext { ... }
    }

    pub mod baz {
        pub struct C2 { .. }
        pub trait C2Ext { ... }
    }
}

References from one class to another

When oxidizing a class C, duchess checks its interface for validity.

First, when the class details were manually specified, duchess will check that they match the Java classes that are available. If the class details are derived automatically from reflection, this isn't necessary.

Next, duchess checks the other classes that are referenced from the oxidized methods of C or via extends/implements. If those classes are part of a package that we are oxidizing, then you get an error if those classes are not being oxidized.

For example, this code would create an error because p.C1 extends p.C2 but p.C2 is not oxidized:

package p;

class C1 extends p.C2 { }
//               ----
//
// ERROR: `C2` is part of package `p`, but not oxidized!

To fix it, either remove the extends declaration or reflect C2 as well:

package p;

class C1 extends p.C2 { }

class C2 { /* you don't have to oxidize any further details */ }

References to other packages

When your classes reference other packages that are not currently being oxidixed, duchess will simply generate a reference to those classes. Its your responsibility to bring them into scope.

// Bring `q` into scope from somewhere else
use some_rust_crate::q;

duchess::java_package! {
    package p;

    class C1 extends q.C2 { }
    //               ----
    //
    // Package `q` is not being oxidized,
    // so duchess just generates a reference
    // to `q::C2`. This will get errors if you have
    // not brought `q` into scope somehow.
}