Is StructuralEq implemented for values rather than for types?

The code below attempts to match an f32 value.

const NAN: f32 = f32::NAN;

match f32_value {
    // These constant patterns are allowed.
    0.0 => (),
    f32::INFINITY => (),

    // error: cannot use NaN in patterns
    // evaluates to `NaN`, which is not allowed in patterns
    f32::NAN => (),
    NAN => (),

    _ => (),
}

NaN is never equal to any value, so I can see why matching against NaN is disallowed. That’s not my concern here; my question is what StructuralEq is and how it is implemented.

The page for StructuralPartialEq says that it is a "Required trait for constants used in pattern matches."

Does that mean the constant 0.0 implements StructuralEq but the constant f32::NAN does not? Is StructuralEq implemented per value rather than per type?

I'm so confused because the values appearing in the code are all the same type: f32. How can one f32 value implement StructuralEq while another does not?

1 Like

Pattern matching is compiler magic, the exact semantics cannot be replicated in the surface language. As the documentation of StructuralEq says,

This is a hack to work around a limitation in our type system.

I actually thought that pattern matching floats is disallowed; it seems there was an intention to do just that back around 2017, but it was only ever a lint, and was removed in 2023 in favor of just making matching against NaNs a hard error. Floats don't implement StructuralEq, but that's fine because they're primitive types and have their own special semantics. The trait exists primarily to allow matching against constants of user-defined types (and, IIUC, to eventually support const generic parameters of user-defined types).

3 Likes

The pattern check is value-based. The relevant property for values is structural equality. The trait is one part of deciding if a value has structural equality, but not the whole story.

See here:

Note how the variant of an enum value is also taken into consideration (so a const None::<T> is always usable).

3 Likes

The checks done for const patterns are described at Patterns - The Rust Reference

You are linking to outdated docs. StructuralEq doesn't exist any more. Only StructuralPartialEq exists. StructuralPartialEq in std::marker - Rust

1 Like

After confirming that the constant pattern has structural equality, how does the compiler compare the value given to match and the constant pattern?

We say that a type that derives PartialEq has “structural equality”: the equality of this type is defined fully by the equality on its fields.

3535-constants-in-patterns - The Rust RFC Book (#guide-level-explanation)


After ensuring all conditions are met, the constant value is translated into a pattern, and now behaves exactly as-if that pattern had been written directly.

3535-constants-in-patterns - The Rust RFC Book (#reference-level-explanation)

Does this mean the compiler compares the value and constant pattern in a field-by-field way like this? If so, what happens if a private field exists in the value?
value.a == pattern.a && value.b == pattern.b && ...

Or does it compare at once like this?
value == pattern

Or is #[derive(PartialEq)] required, but its implementation not actually used? If so, how is the comparison done?

Const patterns are compared for equality on a field-by-field basis, recursively, ignoring privacy. However, in stable rust, this will always have the same behavior with the PartialEq impl. (Except for a bug.)

2 Likes

@haabi May I know how you ended up on that outdated documentation page? Was it linked from somewhere?

I just searched for "Rust StructuralEq" on DuckDuckGo. I can confirm that it appears at the top of the results on both DuckDuckGo and Google in my environment.

1 Like