Rust: Restrict use of constants in patterns (RFC 1445)

Created on 5 Feb 2016  路  13Comments  路  Source: rust-lang/rust

Tracking issue for rust-lang/rfcs#1445.

Implementation steps:

  • [x] disable pattern expansion for user-defined types unless tagged with #[structural_match]
  • [x] disable pattern matching for floating point constants
  • [x] have #[derive(Eq)] add #[structural_match] attribute
  • [ ] exhaustiveness, dead-code integration
  • [ ] #62614, a bug that led us to rollback on #62411 .
  • [x] #63438: move from #[structural_match] attribute to something with impl items (e.g. a Structural trait)
A-patterns B-RFC-approved C-tracking-issue T-lang

Most helpful comment

One original goal of RFC 1445 is to address the weakened abstraction boundary. But looks like it doesn't consider the leak through error messages.

mod state {
    use std::borrow::Cow;

    #[derive(PartialEq, Eq)]
    pub struct State(Cow<'static, str>);
    impl State {
        pub fn new(s: &'static str) -> Self {
            State(Cow::Borrowed(s))
        }
    }

    pub const COLD: State = State(Cow::Borrowed("cold"));
}

fn main() {
    let s = state::State::new("hot");
    match s {
        state::COLD => (),
        _ => (),
    }
}

The error message:

error: to use a constant of type std::borrow::Cow in a pattern, std::borrow::Cow must be annotated with #[derive(PartialEq, Eq)]

exposes the fact that std::borrow::Cow is used under the hood, which should be mere implementation detail that doesn't concern the user. Diagnostically it is also confusing.

All 13 comments

Why #[derive(Eq)] and not #[derive(PartialEq)]? I see this was brought up in the RFC thread but I didn't really see any reasons given.

See the PR #32199

@durka

Why #[derive(Eq)] and not #[derive(PartialEq)]? I see this was brought up in the RFC thread but I didn't really see any reasons given.

The primary reason is that types which only implement PartialEq are not really compatible with a "structural" interpretation. For example, imagine you have a match that tests against the floating point value NaN -- should this match, or not? Consider that NaN != NaN (and hence floating point types are not Eq). Currently, we sidestep this by disallowing a constant of NaN, but we won't be able to do that in the future. And there are other weird examples. Consider 0 vs -0 -- these are distinct values (different bitpatterns) and yet they compare equal. So should match foo { 0 => ... } trigger if foo is -0? Today, it does, but that is a special case for floating point values, and we don't afford user-defined types the same courtesy (that is, we use a strict structural match for user-defined types).

If we wind up adopting a "semantic" interpretation, then we could consider loosening to PartialEq.

Nominating for status update.

@nikomatsakis status update?

@brson this is basically implemented, except that I don't think I did anything clever for the exhaustiveness check. I updated the check marks. The semantics are still (in my mind) basically a kind of temporary hack.

Triage: @nikomatsakis any movement here?

Came here via #62339/#62411/#62614

use std::rc::Rc;
use std::error::Error;

const OK: Result<(), Rc<dyn Error>> = Ok(());

fn main() {
    let is_ok = match OK {
        OK => true,
        _ => false,
    };
}

On the current nightly this complains about not being able to structurally match Rc. I'm never doing that. I feel like this code should just work (as it does on stable)?

What is still left to be done for this issue?

What is still left to be done for this issue?

  • [ ] There is #62614, which is a bug that led us to rollback on #62411 . We should fix both of those things.

    • [ ] There is #63438

    • [ ] I'm not sure what the plan is with respect to exhaustiveness checking, but that check-box remains unchecked.

63438 is closed. However on rustdoc page it seems the primitive types are missing...: https://doc.rust-lang.org/nightly/std/marker/trait.StructuralEq.html

One original goal of RFC 1445 is to address the weakened abstraction boundary. But looks like it doesn't consider the leak through error messages.

mod state {
    use std::borrow::Cow;

    #[derive(PartialEq, Eq)]
    pub struct State(Cow<'static, str>);
    impl State {
        pub fn new(s: &'static str) -> Self {
            State(Cow::Borrowed(s))
        }
    }

    pub const COLD: State = State(Cow::Borrowed("cold"));
}

fn main() {
    let s = state::State::new("hot");
    match s {
        state::COLD => (),
        _ => (),
    }
}

The error message:

error: to use a constant of type std::borrow::Cow in a pattern, std::borrow::Cow must be annotated with #[derive(PartialEq, Eq)]

exposes the fact that std::borrow::Cow is used under the hood, which should be mere implementation detail that doesn't concern the user. Diagnostically it is also confusing.

CTFE forbids comparing pointers for equality, since two pointers being unequal may arbitrarily change to the two pointers being equal depending on various factors (e.g. different constants sharing the same memory or not depending on whether they were instantiated in the same crate).

Should we thus also exclude raw pointers from #[structural_match]?

Was this page helpful?
0 / 5 - 0 ratings