Do Equatable need to be immutable

71 views Asked by At

Is there real case scenarios of not following immutable leading to wrong hash outcome?

I've made a test case to test the non-immutable class and was expecting it to have an error. In reality we do not have any problems with that and HashSet actually works fine here.

Can you show us a simple real case scenario where not following immutable will lead to wrong hashcode value

import 'dart:collection';

import 'package:equatable/equatable.dart';
import 'package:flutter_test/flutter_test.dart';

class _EquatableTest extends Equatable {
  final HashSet values;

  const _EquatableTest({required this.values});

  @override
  List<Object> get props => [values];

  _EquatableTest copyWith({
    HashSet? values,
  }) {
    return _EquatableTest(
      values: values ?? this.values,
    );
  }
}

class _EquatableTestNonImmutable extends Equatable {
  HashSet values;

  _EquatableTestNonImmutable({required this.values});

  @override
  List<Object> get props => [values];
}

class _AdditionalValue extends Equatable {
  HashSet<String> letters;

  _AdditionalValue({required this.letters});

  void add(String letter) {
    letters.add(letter);
  }

  @override
  List<Object> get props => [letters];
}

void main() {
test('NonImmutable created same with _AdditionalValue value', () {
    _EquatableTest equatableImmune1;
    _EquatableTest equatableImmune2;

    equatableImmune1 = _EquatableTest(values: HashSet<_AdditionalValue>());
    equatableImmune2 = _EquatableTest(values: HashSet<_AdditionalValue>());

    equatableImmune1.values.add(_AdditionalValue(letters: HashSet.from(['a', 'b'])));
    equatableImmune1.values.add(_AdditionalValue(letters: HashSet.from(['c', 'd'])));

    equatableImmune2.values.add(_AdditionalValue(letters: HashSet.from(['a', 'b'])));
    equatableImmune2.values.add(_AdditionalValue(letters: HashSet.from(['c', 'd'])));

    expect(equatableImmune1, equatableImmune2);
  });

  test('NonImmutable changed same with _AdditionalValue value', () {
    _EquatableTest equatableImmune1;
    _EquatableTest equatableImmune2;

    equatableImmune1 = _EquatableTest(values: HashSet<_AdditionalValue>());
    equatableImmune2 = _EquatableTest(values: HashSet<_AdditionalValue>());

    _AdditionalValue value1 = _AdditionalValue(letters: HashSet<String>());
    _AdditionalValue value2 = _AdditionalValue(letters: HashSet<String>());

    value1.add('a');
    value2.add('a');

    equatableImmune1.values.add(value1);
    equatableImmune2.values.add(value2);

    expect(equatableImmune1, equatableImmune2);

    if (equatableImmune1.values.first.runtimeType == _AdditionalValue &&
        equatableImmune2.values.first.runtimeType == _AdditionalValue) {
      equatableImmune1.values.first.add('b');
      equatableImmune2.values.first.add('b');
    }

    expect(equatableImmune1, equatableImmune2);
    expect(equatableImmune1.hashCode, equatableImmune2.hashCode);
  });
}
1

There are 1 answers

4
jamesdlin On

Is there real case scenarios of not following immutable leading to wrong hash outcome?

A trivial example:

class Mutable {
  int x;

  Mutable(this.x);

  @override
  bool operator==(Object other) => other is Mutable && x == other.x;

  @override
  int get hashCode => x.hashCode;
}

void main() {
  var m = Mutable(0);

  var set = {m};
  print(set.contains(Mutable(0))); // Prints: true

  m.x = 1;
  print(set.contains(Mutable(1))); // Prints: false

  print(set.contains(set.first)); // Prints: false
}

So now the Set is in an inconsistent state where it doesn't even think it contains its only element.