Skip to content

Instantly share code, notes, and snippets.

@PoignardAzur
Last active July 8, 2020 12:42
Show Gist options
  • Select an option

  • Save PoignardAzur/4ab0f56f160cac2b449eeeba4af50bb1 to your computer and use it in GitHub Desktop.

Select an option

Save PoignardAzur/4ab0f56f160cac2b449eeeba4af50bb1 to your computer and use it in GitHub Desktop.
MCP - Expand object safety

Proposal

NOTE: There has been some debate in the past as to what term best describes when a trait can be turned into a trait object with the syntax dyn MyTrait. The current official term is "object safety", but it is widely agreed to be unsatisfying. We use "dyn-safety" in this document.

Summary

  • Progressively move the language towards having by-default dyn-safety for all traits.

  • To that end, add a new ?dyn trait qualifier to indicate that template parameters accept a trait object.

  • Loosen dyn-safety restrictions. Allow traits with associated functions, associated constants and generic methods to be made into trait objects (with some caveats related to ?dyn).

  • Plan a long-term roadmap for more restrictions to be removed over time, so that eventually impl SomeTrait and dyn SomeTrait in function arguments are functionally equivalent.

Motivation

There are frequent requests to make Rust friendlier towards runtime polymorphism, aka vtables. Use cases include:

On the other hand, in current Rust, a given trait MyTrait can only be used dynamically if it is dyn-safe; specifically, if it obeys a specific set of rules. The aim of these rules is to allow developers to pass compile-time implementations and trait objects interchangeably to any interface specying that it wants an instance of MyTrait.

This proposal claims that a different set of rules should be adopted, that still allows trait-objects and compile-time types to be used interchangeably, while expanding the scope of traits that can be used as trait objects.

The new set of rules would be:

  • For each trait, internally compute a dyn-safe subset of that trait. For instance, that subset would exclude associated functions and associated constants.

  • Add a ?dyn trait qualifier to template parameters. Possible syntax:

    fn my_func<T: ?dyn MyTrait>(x: &T) {
      x.do_something();
    }
    • If the ?dyn qualifier isn't used, the current rules apply.

    • If the ?dyn qualifer is used, my_func is only allowed to use the dyn-safe subset of MyTrait.

  • Allow trait objects to be created for traits with associated functions and associated constants.

    • If a trait has associated functions and associated constants, it can only be passed to a function expecting a ?dyn version of that trait.

    • Objects that are currently dyn-safe can still be passed as normal.

  • Allow trait objects to have generic methods and be dyn-safe, if every generic type argument is bound to a dyn-safe trait or the dyn-safe subset of a trait. Example:

    trait DebugPrinter {
      // DebugPrint is dyn-safe, because std::fmt::Debug is dyn-safe
      fn print_debug<D: std::fmt::Debug>(&mut self, data: &D);
    }
    • In that case, a default implentation is generated that takes a trait object for every generic type the method expects.

This new set of rules would allow trait objects to be made for a wider set of traits (traits with associated functions/constants, traits with generic methods) with no modification of existing code. In particular, this would allow developers to use traits in existing libraries that weren't designed with trait objects in mind, in contexts where trait objects are necessary.

NOTE: This proposal deliberately doesn't mention Sized. It's written with the assumption that, if/when RFC-1909 and RFC-2884 are implemented, size will matter less to the language over time, and syntax will eventually be changed accordingly. Strictly speaking, the examples would probably need + ?Sized annotations to compile, barring an edition change.

Breaking changes

None that I can see.

This feature should be strictly additive.

Future improvements

  • Have impl MyTrait arguments use the dyn-safe subset of MyTrait by default (breaking change).

  • Add a syntax to access associated functions/constants from a trait object (eg <typeof my_trait_object>::static_method(...)); this would require that additional data be stored in the vtable.

  • Perform the step above automatically in cases where the relevant vtable can be trivially found from an argument.

MCP Checklist

  • MCP filed.
    • The @rust-lang/wg-prioritization group will add this to the triage meeting agenda so folks see it.
    • A Zulip topic in the stream #t-compiler/major changes will be created for this issue.
  • MCP seconded. The MCP is "seconded" when a compiler team member or contributor issues the @rustbot second command. This should only be done by someone knowledgable with the area -- before seconding, it may be a good idea to cc other stakeholders as well and get their opinion.
  • Final comment period (FCP). Once the MCP is approved, the FCP begins and lasts for 10 days. This is a time for other members to review and raise concerns -- concerns that should block acceptance should be noted as comments on the thread, ideally with a link to Zulip for further discussion.
  • MCP Accepted. At the end of the FCP, a compiler team lead will review the comments and discussion and decide whether to accept the MCP.
    • At this point, the major-change-accepted label is added and the issue is closed. You can link to it for future reference.

Mentors or Reviewers

No mentor yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment