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
- Implement LanguageExt.Traits.Hashable<T> on the struct passed to [Hashable] and return its GetHashCode.
- Migrate legacy hashers to the Traits namespace interface when upgrading LanguageExt.
- 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
- Implement LanguageExt.Traits.Hashable<T> explicitly on a dedicated struct.
- Do not reuse legacy Hashable implementations across major upgrades without migration.
- Smoke-test record hashing (e.g. add records to a HashSet) in CI.
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
- Ord attribute should have a struct type that derives from…
- Eq attribute should have a struct type that derives from…
- Don't use Equals - use either RecordType
- Don't use Equals - use either RecordType
- s
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)