Skip to content

Simplify toolchain type hierarchy #5092

Open

Description

@rami3l

It feels to me like we have a lot of different "transition" and a whole bunch of supporting code, but we should use a small number of toolchain name types throughout the codebase and proactively resolve to these early/upfront.

It would be good to summarize the design of these and see if some of it can be flattened.

Originally posted by @djc in #5071 (comment)

Tasks

Activity

  1. self-assigned this
    on Sep 17, 2026
  2. rami3l commented on Sep 23, 2026

    @rami3l
    MemberAuthor

    Below is a short summary of rustup's toolchain hierarchy at the time of writing. Type declarations are intentionally written in TypeScript style since the focus is really the data each type contains, rather than the variants of each tag union as implemented in Rust.

    My own comments are written like this.

    Toolchain name abstraction

    [Partial]ToolchainDesc

    Describes an official toolchain name.

    This family has two variants:

    type PartialToolchainDesc = _DatedChannel & { target: PartialTargetTuple }
    type ToolchainDesc = _DatedChannel & { target: TargetTuple }

    ... where:

    type _DatedChannel = { channel: Channel; date: string? }

    Note that in a PartialTargetTuple, blanks are allowed. In other words, the below are all valid partial tuples:

    [
      "i386", "pc-windows", "gnu",
      "i386-gnu", "pc-windows-gnu", "i386-pc-windows",
      "i386-pc-windows-gnu",
    ]

    When converting the former to the latter via .resolve() , the host tuple will be passed in to fill in the blanks.

    [Resolvable][Local]ToolchainName

    Describes a local or an official toolchain name.

    This family has two variants, and the latter adds the possibility of a "path-based toolchain name" onto the former.

    // A partial official toolchain name, or a custom toolchain name (like 'system')
    type ResolvableToolchainName = PartialToolchainDesc | CustomToolchainName
    // A partial official toolchain name, or a custom toolchain name, or a path
    type ResolvableLocalToolchainName = ResolvableToolchainName | PathBasedToolchainName

    Their respective fully resolved forms are ToolchainName and LocalToolchainName:

    // A _full_ official toolchain name, or a custom toolchain name
    type ToolchainName = ToolchainDesc | CustomToolchainName
    // A _full_ official toolchain name, a custom toolchain name, or a path
    type LocalToolchainName = ToolchainName | PathBasedToolchainName

    The final authoritative result type is LocalToolchainName, which will be used later in the toolchain types.

    Toolchain name wrappers

    Maybe(Official|Resolvable)ToolchainName

    Non-generic wrappers over the underlying type and the reserved name none.

    This family has two variants:

    // 'none', or a partial official toolchain name
    // Used in `rustup-init --default-toolchain=xxx`
    type MaybeOfficialToolchainName = 'none' | PartialToolchainDesc
    // 'none', or a partial official toolchain name, or a custom toolchain name
    // Used in `rustup default yyy` in conjunction with `Override<>`, see below.
    type MaybeResolvableToolchainName = 'none' | ResolvableToolchainName

    The naming here is really not consistent and very confusing.

    Override<T>

    Generic wrappers over the underlying type and the reserved name default (which can later be extended to env etc.).

    type Override<T> = ToolchainAlias | T
    type ToolchainAlias = 'default' // | 'env'

    This family current has three instances (note that T is always unresolved):

    // An alias, a partial official toolchain name, or a custom toolchain name
    // Used in `rustup override set xxx`
    Override<ResolvableToolchainName>;
    // An alias, a partial official toolchain name, or a custom toolchain name, or a path
    // Used in `rustup +yyy`
    Override<ResolvableLocalToolchainName>;
    // An alias, 'none', a partial official toolchain name, or a custom toolchain name
    // Used in `rustup default yyy`
    Override<MaybeResolvableToolchainName>;

    I feel like it might be interesting to fold all layers with potential reserved names into a single wrapper type or a clear hierarchy, if possible.

    Toolchain types

    There are two variants:

    type Toolchain = { cfg: Cfg; name: LocalToolchainName; path: PathBuf }
    // Inherits `Toolchain` but is de facto limited to official ones as enforced by the constructor.
    // It has special methods specific to official toolchains.
    // `desc` is cloned from `.toolchain.name`'s `ToolchainDesc` variant, looks redundant.
    type DistributableToolchain = { toolchain: Toolchain; desc: ToolchainDesc }

    Maybe this can also be a generic type among the lines of Toolchain<OfficialToolchainName> and Toolchain<LocalToolchainName>, with particular implementations just for the former case.

  3. rami3l commented on Sep 23, 2026

    @rami3l
    MemberAuthor

    Regarding the toolchain names, I feel like we should do the following renames:

    • [Partial]ToolchainDesc -> [Partial]OfficialToolchainName: to align with CustomToolchainName
    • [Resolvable]ToolchainName -> [Partial]ToolchainName
    • PathBasedToolchainName -> ToolchainPath
    • [Resolvable]LocalToolchainName -> [Partial]ToolchainNameOrPath

    We still need to design the verb to be used when converting from PartialFooName to FooName, and from Override<Foo> to Foo. Currently, both are called .resolve(), but actually I would prefer going with #5041's naming and rename the first to .qualify().

  4. djc commented on Sep 23, 2026

    @djc
    Contributor

    Which toolchain names/types do we actually end up using deeper in callgraphs?

  5. rami3l commented on Sep 23, 2026

    @rami3l
    MemberAuthor

    @djc I'd say the core types are (in their current names) LocalToolchainName in the toolchain name layer, and Toolchain and DistributableToolchain in the toolchain layer.

    Official toolchains and have a special toolchain type DistributableToolchain, which is the more capable of the two toolchain types (because it has a manifest file and can install components, targets, etc.). However the DistributableToolchain case is more independent from that of other types, so its redesign can probably be postponed.

    Other types are really mostly FromStr adapters for one or more of the 3 cases of LocalToolchainName: the official toolchain name, the custom toolchain name, and the toolchain path. This means that they are also considered to be a part of the toolchain name layer. The only differences are: some of them are unqualified (with a partial target tuple), some aliased (with an alias), some optional (with a possible 'none').

    In conclusion: the data flows from the toolchain name layer to the toolchain layer, as rustup generally first converts a toolchain string from those FromStr adapters to LocalToolchainName, and then construct Toolchain and/or DistributableToolchain from it.

  6. djc commented on Sep 23, 2026

    @djc
    Contributor

    So then it feels like we should take all types that are not LocalToolchainName, Toolchain, DistributableToolchain and ToolchainDesc and make it easy to convert them into one of those, right?

  7. rami3l commented on Sep 23, 2026

    @rami3l
    MemberAuthor

    @djc Yes, and I believe that is globally already the case, although the conversion inside the toolchain name layer is a mess when looking pretty close.

    Personally, if I were new to the codebase, I want the names of the types to look closely related to LocalToolchainName and ToolchainDesc as much as possible so that I know immediately which types are from which of the 2 layers and may correspond to which of the 3 cases above. I'm looking into just that.

  8. rami3l commented on Sep 25, 2026

    @rami3l
    MemberAuthor

    So I did a quick test with GPT in rami3l@f6cdfdb and found out that Toolchain and DistributableToolchain can indeed be unified as:

    pub(crate) struct Toolchain<'a, T = LocalToolchainName> { ... }
    pub(crate) type DistributableToolchain<'a> = Toolchain<'a, ToolchainDesc>;
    
    pub(crate) trait GenericToolchainName: Clone + Debug + Display {
        fn path(&self, cfg: &Cfg<'_>) -> PathBuf;
        fn exists(&self, cfg: &Cfg<'_>) -> Result<bool, RustupError>;
        fn as_official(&self) -> Option<&ToolchainDesc>;
        ...
    }
    
    impl GenericToolchainName for LocalToolchainName;
    impl GenericToolchainName for ToolchainName;
    impl GenericToolchainName for ToolchainDesc;

    Although perfect naming would be ToolchainName instead of GenericToolchainName, which would require renaming ToolchainName to something else.

    I think that would be a very good starting point.

  9. djc commented on Sep 25, 2026

    @djc
    Contributor

    Does look like an improvement!

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions