[Pre-RFC] Provide flavors for libraries (non-additive features)


#1

Motivation

We already have features, which allow for additional code to be compiled, only if the user needs it. In a case, where two libraries depend on such a library, one library might need a feature the other library does not need. The solution is to compile all requested features, which is only guaranteed to work if every feature is purely additive. However, suppose you want to write a graphics library with support for different backends. The most common solution is to use traits and type parameters in order to allow this, which leads to over-generalization (who wants to use multiple backends at the same time) and often, a dependent library uses type definitions (like type Window = graphics::Window<GlBackend>). What would be helpful here, is an option to select a backend in the Cargo.toml. This is what flavors are about.

Design

Note: Just some rough ideas to start the discussion

  • Add a new table to Cargo.toml, [flavors]
  • Every flavor is another table
  • If flavors conflict, link to the library twice (like cargo is already doing for two incompatible versions)

Drawbacks

Not yet resolved / depending on design choices

  • Bigger executable size if incompatible flavors are used
    • Are flavors mostly used in libraries with just a single dependent library?
    • Can we minimize code duplication if a library has multiple flavors, all with structs / functions they have in common?
    • Should the a library foo offering flavors provide all common items in a dependent library, foo-core?

Alternatives

  • In some situations, traits & type parameters
    • not possible to provide different functionality
    • eventually more complex code
  • Code duplication: GlWindow, VkWindow, DxWindow
    • may be macro-provided
  • features, not keeping their promises
  • one library per flavor

Unresolved question

  • Which guarantees does a library make about flavors? Should another flavor always provide the same items (checked by the compiler)?
    • -> you cannot forget anything
    • more limited, flavors cannot offer different functionality
  • Should there be just one set of flavors or multiple flavors like this:
[flavors.backend]
flavors = ["opengl", "vulkan"]

[flavors.threadpool]
flavors = ["rayon", "rust-threadpool"]
  • How do flavors work together with features? How to specify dependencies of a flavor?
  • What drawbacks are there?
  • Many, many more

Thanks for reading this, feel free to make suggestions on how to improve this.


#2

I highly support efforts in this direction.

In a crate where I’m (mis)using features for this, I have distinct sets of flavors. You don’t write about this.

In some cases it might be necessary to prohibit linking a crate twice with different flavors. For example, you’re currently allowed to link to a native library only once in your crate dependency graph. This is what -sys crates are for. More in general, a crate might assume that it’s the global owner of a particular resource (hardware device, file network port, etc.).


#3

Thanks for your reply.

In a crate where I’m (mis)using features for this, I have distinct sets of flavors. You don’t write about this.

Isn’t that the same as “Should there be just one set of flavors or multiple flavors like this” in the Unresolved questions? I agree we need multiple sets of features.

In some cases it might be necessary to prohibit linking a crate twice with different flavors.

Yes, thanks for pointing this out!

Additionally, because flavors very often require different dependencies, the above method with arrays for flavors isn’t good. So I’d propose a Cargo.toml like this:

[flavors.backend]
double-linkage = false // Optional, prohibits linking twice on a flavor conflict
default = "opengl" // Optional, otherwise error if user didn't specify a flavor

[flavors.backend.flavors]
opengl = { dependencies = ["gl"] }
directx = { dependencies = ["directx-sys"], cfg = "target_os = windows" }

Alternative: Use an array of tables


#4

All this is already a problem with different versions of the same crate.