Skip to content

Commit

Permalink
Add two draft project goals: seamless C support, and relaxing the orp…
Browse files Browse the repository at this point in the history
…han rule

Both WIP, both without owners yet.

These are meant as samples of goals, and as samples of goals without
owners specifically. However, they're also goals that people have
expressed support for solving.
  • Loading branch information
joshtriplett committed May 6, 2024
1 parent 761a32b commit d3d8d44
Show file tree
Hide file tree
Showing 3 changed files with 274 additions and 1 deletion.
132 changes: 132 additions & 0 deletions src/2024h2/Relaxing-the-Orphan-Rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Relaxing the Orphan Rule

| Metadata | |
| --- | --- |
| Owner(s) | |
| Teams | *lang* |
| Status | WIP |

## Motivation

Relax the orphan rule, in limited circumstances, to allow crates to provide
implementations of third-party traits for third-party types. The orphan rule
averts one potential source of conflicts between Rust crates, but its presence
also creates scaling issues in the Rust community: it prevents providing a
third-party library that integrates two other libraries with each other, and
instead requires convincing the author of one of the two libraries to add
(optional) support for the other, or requires using a newtype wrapper. Relaxing
the orphan rule, carefully, would make it easier to integrate libraries with
each other, share those integrations, and make it easier for new libraries to
garner support from the ecosystem.

### The status quo

Suppose a Rust developer wants to work with two libraries: `lib_a` providing
trait `TraitA`, and `lib_b` providing type `TypeB`. Due to the orphan rule, if
they want to use the two together, they have the following options:

- Convince the maintainer of `lib_a` to provide `impl TraitA for TypeB`. This
typically involves an optional dependency on `lib_b`. This usually only
occurs if `lib_a` is substantially less popular than `lib_b`, or the
maintainer of `lib_a` is convinced that others are likely to want to use the
two together. This tends to feel "reversed" from the norm.

- Convince the maintainer of `lib_b` to provide `impl TraitA for TypeB`. This
typically involves an optional dependency on `lib_a`. This is only likely to
occur if `lib_a` is popular, and the maintainer of `lib_b` is convinced that
others may want to use the two together. The difficulty in advocating this,
scaled across the community, is one big reason why it's difficult to build
new popular crates built around traits (e.g. competing
serialization/deserialization libraries, or competing async I/O traits).

- Vendor either `lib_a` or `lib_b` into their own project. This is
inconvenient, adds maintenance costs, and isn't typically an option for
public projects intended for others to use.

- Create a newtype wrapper around `TypeB`, and implement `TraitA` for the
wrapper type. This is less convenient, propagates throughout the crate (and
through other crates if doing this in a library), and may require additional
trait implementations for the wrapper that `TypeB` already implemented.

All of these solutions are suboptimal in some way, and inconvenient. In
particular, all of them are much more difficult than actually writing the trait
impl. All of them tend to take longer, as well, slowing down whatever goal
depended on having the trait impl.

### The next few steps

As an initial experiment, try relaxing the orphan rule for binary crates, since
this cannot create library incompatibilities in the ecosystem. Allow binary
crates to implement third-party traits for third-party types, possibly
requiring a marker on either the trait or type or both. See how well this works
for users.

As a second experiment, try allowing library crates to provide third-party
impls as long as no implementations actually conflict. Perhaps require marking
traits and/or types that permit third-party impls, to ensure that crates can
always implement traits for their own types.

### The "shiny future" we are working towards

Long-term, we'll want a way to resolve conflicts between third-party trait
impls.

We should support a "standalone derive" mechanism, to derive a trait for a type
without attaching the derive to the type definition. We could save a simple
form of type information about a type, and define a standalone deriving
mechanism that consumes exclusively that information.

Given such a mechanism, we could then permit any crate to invoke the standalone
derive mechanism for a trait and type, and allow identical derivations no
matter where they appear in the dependency tree.

## Design axioms

- **Rustaceans should be able to easily integrate a third-party trait with a
third-party type without requiring the cooperation of third-party crate
maintainers.**

- **It should be possible to *publish* such integration as a new crate.** For
instance, it should be possible to publish an `a_b` crate integrating `a`
with `b`. This makes it easier to scale the ecosystem and get adoption for
new libraries.

- **Crate authors should have some control over whether their types have
third-party traits implemented.** This ensures that it isn't a breaking
change to introdice first-party trait implementations.

[da]: ../about/design_axioms.md

## Ownership and other resources

**Owner:** TODO

### Support needed from the project

* Lang team:
* Design meetings to discuss design changes
* RFC reviews
* Blog post inviting testing, evaluation, and feedback

## Outputs and milestones

### Outputs

The output will be a pair of RFCs:
- A lang RFC proposing a very simple system for binaries to ignore the orphan rule.
- A lang RFC proposing a system with more careful safeguards, to relax the orphan rule for publishable library crates.

### Milestones

- Accepted RFCs.

## Frequently asked questions

### Won't this create incompatibilities between libraries that implement the same trait for the same type?

Yes! The orphan rule is a tradeoff. It was established to avert one source of
potential incompatibility between library crates, in order to help the
ecosystem grow, scale, and avoid conflicts. However, the presence of the orphan
rule creates a different set of scaling issues and conflicts. This project goal
proposes to adjust the balance, attempting to achieve some of the benefits of
both.
137 changes: 137 additions & 0 deletions src/2024h2/Seamless-C-Support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Seamless C support

| Metadata | |
| --- | --- |
| Owner(s) | *Github usernames or other identifying info for goal owners* |
| Teams | *Names of teams being asked to commit to the goal* |
| Status | WIP |

## Motivation

Using C from Rust should be as easy as using C from C++: completely seamless,
as though it's just another module of code. You should be able to drop Rust
code into a C project and start compiling and using it in minutes.

### The status quo

Today, people who want to use C and Rust together in a project have to put
substantial work into infrastructure or manual bindings. Whether by creating
build system infrastructure to invoke bindgen/cbindgen (and requiring the
installation of those tools), or manually writing C bindings in Rust, projects
cannot simply drop Rust code into a C program or C code into a Rust program.
This creates a high bar for adopting or experimenting with Rust, and makes it
more difficult to provide Rust bindings for a C library.

By contrast, dropping C++ code into a C project or C code into a C++ project is
trivial. The same compiler understands both C and C++, and allows compiling
both together or separately. The developer does not need to duplicate
declarations for the two languages, and can freely call between functions in
both languages.

C and C++ are still not the same language. They have different idioms and
common types, and a C interface may not be the most ergonomic to use from C++.
Using C++ from C involves treating the C as C++, such that it no longer works
with a C compiler that has no C++ support. But nonetheless, C++ and C integrate
extremely well, and C++ is currently the easiest language to integrate into an
established C project.

This is the level of integration we should aspire to for Rust and C.

### The next few steps

To provide seamless integration between Rust and C, we need a single compiler
to understand both Rust and C. Thus, the first step will be to integrate a C
preprocessor and compiler frontend into the Rust compiler. For at least the
initial experimentation, we could integrate components from LLVM, taking
inspiration from `zig cc`. (In the future, we can consider other alternatives,
including a native Rust implementation. We could also consider components from
c2rust or similar.)

We can either generate MIR directly from C (which would be experimental and
incomplete but integrate better with the compiler), or bypass MIR and generate
LLVM bytecode (which would be simpler but less well integrated).

This first step would provide substantial benefits already: a C compiler that's
always available on any system with Rust installed, that generates code for any
supported Rust target, and that always supports cross-language optimization.

We can further improve support for calling C from Rust. We can support
"importing" C header files, to permit using this support to call external
libraries, and to support inline functions.

### The "shiny future" we are working towards

Once C support is integrated, we can generate type information for C functions
as if they were unsafe Rust functions, and then support treating the C code as
a Rust module, adding the ability to import and call C functions from Rust.
This would not necessarily even require header files, making it even simpler to
use C from Rust. The initial support can be incomplete, supporting the subset
of C that has reasonable semantics in Rust.

We will also want to add C features that are missing in Rust, to allow Rust to
call any supported C code.

Once we have a C compiler integrated into Rust, we can incrementally add C
extensions to support using Rust from C. For instance:
- Support importing Rust modules and calling `extern "C"` functions from
them, without requiring a C header file.
- Support using `::` for scoping names.
- Support simple Rust types (e.g. `Option` and `Result`).
- Support calling Rust methods on objects.
- Allow annotating C functions with Rust-enhanced type signatures, such as
marking them as safe, using Rust references for pointer parameters, or
providing simple lifetime information.

We can support mixing Rust and C in a source file, to simplify incremental
porting even further.

To provide simpler integration into C build systems, we can accept a
C-compiler-compatible command line (`CFLAGS`), and apply that to the C code we
process.

We can also provide a CLI entry point that's sufficiently command-line
compatible to allow using it as `CC` in a C project.

## Design axioms

- **C code should feel like just another Rust module.** Integrating C code into
a Rust project, or Rust code into a C project, should be trivial; it should
be just as easy as integrating C with C++.

- **This is not primarily about providing *safe* bindings.** This project will
primarily make it much easier to access C bindings as unsafe interfaces.
There will still be value in wrapping these unsafe C interfaces with safer
Rust interfaces.

- **Calling C from Rust should not require writing duplicate information in Rust**
that's already present in a C header or source file.

- **Integrating C with Rust should not require third-party tools**.

- **Compiling C code should not require substantially changing the information
normally passed to a C compiler** (e.g. compiler arguments).

## Ownership and other resources

**Owner:** TODO

### Support needed from the project

* Lang team:
* Design meetings to discuss design changes
* RFC reviews
* Compiler team:
* RFC review

## Outputs and milestones

### Outputs

The initial output will be a pair of RFCs: one for an experimental integration of a C compiler into rustc, and the other for minimal language features to take advantage of that.

### Milestones

- Compiler RFC: Integrated C compiler
- Lang RFC: Rust language support for seamless C integration

## Frequently asked questions
6 changes: 5 additions & 1 deletion src/2024h2/slate.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ None
|[Fallible allocation][] | ![Owner needed][own] | |
| [Polonius on nightly][] | [lqd] | [Lang], [Types] |
| [Impl trait everywhere][] | [oli-obk] | [Lang], [Types] |
| [Seamless C Support][] | ![Owner needed][own] | [Lang] |
| [Relaxing the Orphan Rule][] | ![Owner needed][own] | [Lang] |

## Not accepted goals

Expand All @@ -48,6 +50,8 @@ None.
[Return type notation]: ./Async--AsyncClosures.md
[Polonius on nightly]: ./Polonius.md
[Impl trait everywhere]: ./Impl-trait-everywhere.md
[Seamless C Support]: ./Seamless-C-Support.md
[Relaxing the Orphan Rule]: ./Relaxing-the-Orphan-Rule.md

[own]: https://img.shields.io/badge/Owned%20Needed-blue

Expand All @@ -60,4 +64,4 @@ None.
[LC]: https://www.rust-lang.org/governance/teams/leadership-council
[Lang]: https://www.rust-lang.org/governance/teams/lang
[Types]: https://www.rust-lang.org/governance/teams/compiler#team-types
[Libs-API]: https://www.rust-lang.org/governance/teams/library#team-libs-api
[Libs-API]: https://www.rust-lang.org/governance/teams/library#team-libs-api

0 comments on commit d3d8d44

Please sign in to comment.