Cycles during trait resolution


#1

I’m revisiting my include_dir crate to give it a proper include_dir!() macro with proc_macro_hack and encountered an interesting cyclic generics problem.

Basically, I want include_dir!() to expand to some Dir which includes the bytes of each file via include_bytes!() as a &'static [u8]. However, for simplicity of implementation I’d like to use the same Dir type except with all the fields backed by a Vec so I can update a Dir in-place while writing the macro. In theory, this is exactly the use case for Cow<'a, [File<'a>]>.

#[derive(Debug, Clone, PartialEq)]
pub struct Dir<'a> {
    path: Cow<'a, Path>,
    files: Cow<'a, [File<'a>]>,
    dirs: Cow<'a, [Dir<'a>]>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct File<'a> {
    pub path: Cow<'a, Path>,
    pub contents: Cow<'a, [u8]>,
}

Trying to compile results in an error because resolving Dir<'a>: ToOwned requires checking [Dir<'a>]: ToOwned, which in turn requires Dir<'a>: ToOwned.

error[E0275]: overflow evaluating the requirement `<[dir::Dir<'_>] as std::borrow::ToOwned>::Owned`
  |
  = note: required because it appears within the type `dir::Dir<'_>`
  = note: required because of the requirements on the impl of `std::borrow::ToOwned` for `[dir::Dir<'_>]`
  = note: required because it appears within the type `dir::Dir<'_>`

error: aborting due to previous error

Does anyone have any ideas on what I can do to fix the cycle in trait resolution? I could always create two parallel sets of File and Dir, one for compile time which stores its data on the heap and another which is 'static, but I’d prefer not to if possible.

The end goal is being able to generate something like this:

static Foo: Dir<'static> = Dir {
  path: "foo/bar/",
  files: Cow::Borrowed(&'static [
    File {
      path: "foo/bar/baz.txt",
      contents: Cow::Borrowed(&'static [1, 2, 3, 4, ... ]),
    },
    ...
  ]),
  dirs: Cow::Borrowed(&[ ... ]),
};

#2

This feels like a compiler deficiency? Replacing dirs with an array (e.g. dirs: Cow<'a, [Dir<'a>; 32]>) works just fine, but the slice throws it for a loop.

So is the idea that the Owned variant might be used in the non-macro part of the API?


#3

That was the original intention. That way I can share methods between the 'static data (when using the generated data at runtime) and the owned data (when the proc macro is being evaluated).

I remember encountering similar issues in the past and the explanation was that’s how the trait solver currently works. But for now I think I’m just going to suck it up and accept the code duplication.


#4

Looks fairly identical to https://github.com/rust-lang/rust/issues/47032