Skip to content

Instantly share code, notes, and snippets.

@eernstg
Last active January 4, 2024 14:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eernstg/90377a2d4a923b665b1b7435e70bdf50 to your computer and use it in GitHub Desktop.
Save eernstg/90377a2d4a923b665b1b7435e70bdf50 to your computer and use it in GitHub Desktop.
Example showing selective access to a private interface using extension types
// Main idea is "same object, different treatment". We create one
// `_Buffer` object and use it as a `WriteBuffer` or as a `ReadBuffer`.
// The two perspectives on the `_Buffer` yield unrelated types,
// different members, even different variance of the type parameter.
// A client _must_ use one or the other extension type, there is no
// way a caller outside this library can directly call any instance
// member of `_Buffer`.
class _Buffer<X> {
X? _value;
bool _occupied = false;
void _put(X newValue) {
if (_occupied) throw "Error: Cannot put!";
_value = newValue;
_occupied = true;
}
X _get() {
if (!_occupied) throw "Error: Cannot get!";
_occupied = false;
return _value!;
}
}
typedef _Inv<X> = X Function(X);
typedef WriteBuffer<X> = _WriteBuffer<X, _Inv<X>>;
extension type _WriteBuffer<X, Invariance extends _Inv<X>>._(_Buffer<X> it) {
_WriteBuffer(): this._(_Buffer<X>());
ReadBuffer<X> get readBuffer => ReadBuffer._(it);
bool get isNotFull => !it._occupied;
void put(X newValue) => it._put(newValue);
}
extension type ReadBuffer<X>._(_Buffer<X> it) {
bool get isNotEmpty => it._occupied;
X get() => it._get();
}
import 'dart:math';
import 'selective_access.dart';
final random = Random();
bool get perhaps => random.nextBool();
void main() {
var writer = WriteBuffer<int>();
var reader = writer.readBuffer;
for (int i = 0; i < 10; ++i) {
if (perhaps && writer.isNotFull) writer.put(i);
if (perhaps && reader.isNotEmpty) print(reader.get());
}
if (reader.isNotEmpty) print(reader.get());
// The WriteBuffer is invariant, the ReadBuffer is covariant.
// WriteBuffer<num> numWriter = writer; // Compile-time error.
ReadBuffer<num> numReader = reader; // OK.
writer.put(42);
print(numReader.get());
// We can perform dynamic invocations, but only on `Object` members.
// In other words, the buffer interfaces can _only_ be used via the
// extension types.
dynamic d = writer;
d.toString();
d._put(1); // Throws, no such member.
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment