Skip to content

[Feature] position-independent module paths #576

@dwrensha

Description

@dwrensha

The Problem

Suppose we have this schema in a file named example.capnp:

@0xcb795246146676e8;

struct Foo {}

struct Bar {
   foo @0 :Foo;
}

In the code that capnpc-rust generates for this schema, accessor methods for the Bar.foo field refer to the type Foo using a full absolute module path, like crate::example_capnp::foo::Reader. This implies that the generated code must be included at the top level of a crate -- something that not everyone wants to do.

The parentModule annotation provides a way to work around this limitation, but it's cumbersome to use.

Another workaround is the default_parent_module option, but it too does not meet everyone's needs.

We could avoid a lot of awkwardness if the generated code could somehow just work when dropped in at any module location.

A Questionable Solution

We could use Rust's super keyword to make everything relative. Then the type in the above example would be super::foo::Reader, which does not depend on the position of the code in the module structure of the crate.

However, there are two reasons that this approach is not completely satisfactory:

  1. For schema files with nested structs, we would end up with lots of paths like super::super::super::baz::Reader, which seems potentially confusing to humans reading the code.
  2. Tracking the relative paths would require some additional complexity in codegen.rs.

A Better Solution

Inspired by this example from recapn, we can use the super keyword together with the use ... as ... pattern.

pub mod example_capnp {
  use super::example_capnp as _file;
 
  pub mod _imports { ... }

  pub mod foo {
    use super::_file as _file;
    // ...
  }

  pub mod bar {
    use super::_file as _file;
    // ... accessors that refer to `_file::foo::Reader`
  }
}

The idea is to ensure that in every generated module, the module path _file is in scope and refers to the top-level generated module of the corresponding schema file.

To address the case when there are multiple schema files, some of which might import others, we add a mod _imports in every _file module. This module will resolve any cross-file module references that need to happen; the code generator can easily populate it using the RequestedFile.imports field.

We should still keep the parentModule annotation for now, for compatibility purposes, but this new setup should eliminate the need for it in most cases.

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