louthy/language-ext · error · Exception

Hashable attribute should have a struct type that derives…

Error message

Hashable attribute should have a struct type that derives from LanguageExt.Traits.Hashable<> passed as its argument

What it means

HashableAttribute.Validate checks that the type supplied to the [Hashable] attribute implements LanguageExt.Traits.Hashable<T>; if not it throws. Records use this type to compute hash codes, so an invalid argument breaks hashing of the record.

Solutions

  1. Implement LanguageExt.Traits.Hashable<T> on the struct passed to [Hashable] and return its GetHashCode.
  2. Migrate legacy hashers to the Traits namespace interface when upgrading LanguageExt.
  3. Verify the argument is a struct comparer, not the record type or an unrelated type.

Example fix

// before
[Hashable(typeof(MyHash))]  // implements legacy Hashable<Rec>
public partial record Rec(int X);

// after
public readonly struct MyHash : LanguageExt.Traits.Hashable<Rec>
{
    public int GetHashCode(Rec x) => x.X.GetHashCode();
}
[Hashable(typeof(MyHash))]
public partial record Rec(int X);
Defensive patterns

Strategy: validation

Validate before calling

// before applying the attribute
typeof(MyHash).GetInterfaces().Any(i =>
    i.IsGenericType && i.GetGenericTypeDefinition() == typeof(LanguageExt.Traits.Hashable<>));

Type guard

static bool IsValidHasher<T>(T t) => t is LanguageExt.Traits.Hashable<RecordType>;

Prevention

When it happens

Trigger: Decorating a record with [Hashable(typeof(X))] where X's implemented interfaces contain nothing starting with "LanguageExt.Traits.Hashable`1" — e.g. X implements the legacy LanguageExt.Hashable<T> or no hashable interface at all.

Common situations: Upgrading from earlier LanguageExt versions with legacy Hashable<T>; passing a class instead of a struct; passing the record type itself rather than a dedicated hasher.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of louthy/language-ext@2f0e362824 (2026-09-15). Data as JSON: /api/errors/e399ef749346e527. Report an issue: GitHub.

Appendix: source

Thrown at LanguageExt.Core/DataTypes/Record/Attributes.cs:55

                 .ImplementedInterfaces
                 .AsIterable()
                 .Exists(i => i.ToString().StartsWith("LanguageExt.Traits.Ord`1")))
        {
            throw new Exception("Ord attribute should have a struct type that derives from LanguageExt.Traits.Ord<> passed as its argument");
        }
    }
}

public class HashableAttribute : Attribute
{
    public HashableAttribute(Type type)
    {
        if (!type.GetTypeInfo()
                 .ImplementedInterfaces
                 .AsIterable()
                 .Exists(i => i.ToString().StartsWith("LanguageExt.Traits.Hashable`1")))
        {
            throw new Exception("Hashable attribute should have a struct type that derives from LanguageExt.Traits.Hashable<> passed as its argument");
        }
    }
}

/// <summary>
/// Stops the base type fields being used for any Record operations
/// </summary>
/// <remarks>This is *not* used for the [Record] code-gen</remarks>
public class IgnoreBaseAttribute : Attribute;

View on GitHub (pinned to 2f0e362824)