Ownership pattern matching questions

I'm exploring ownership accessors by re-implementing Rust types in Swift.

public enum Cow<B : ToOwned & ~Copyable> : Borrow, Clone, ~Escapable {
	/// Borrowed data.
	case borrowed(Ref<B>)
	/// Owned data.
	case owned(B.Owned)

	public var value: B {
		borrow {
			switch self {
			case let .borrowed(b): b.value
			case let .owned(b): b.value
			}
		}
		mutate {
			switch self {
			case let .borrowed(b):
				self = .owned(b.value.toOwned())
				switch self {
				case .owned(var o): return &o.value
				default: unsafeBitCast((), to: Never.self)
				}
			case var .owned(o):
				return &o.value
			}
		}
	}

	public func clone() -> Self {
		switch self {
		case let .borrowed(b): .borrowed(b)
		case let .owned(o): .owned(o.value.toOwned())
		}
	}

	mutating public func clone(from source: borrowing Cow) {
		switch (self, source) {
		case (var .owned(dest), let .owned(o)): o.value.clone(into: &dest)
		default: self = source.clone()
		}
	}
}

I'm getting the following error on value: B on each return:

  • Invalid return value from a mutate accessor
  • Mutate accessors can return stored properties, computed properties with mutate accessors or global 'let' declarations

The .value refers to the computed property ({ borrow mutate }) in the Borrow protocol so I'd expect it to work.

What am I doing wrong?

1 Like

In both branches, you're returning (part of) the local var o, not a property of self. I'm not sure how to make this work, though, for lack of some sort of inout switch.

1 Like

I've tried to implement the same thing before. You can't really do it yet for a few reasons. The main one is that enums don't promise to hold the payload in a 'valid' representation for the type in memory. Swift uses a "spare bit" optimization for enum layouts sometimes where the discriminator is packed into the payload and actually needs to be masked out + re-applied when you access the payload, so there is no valid in-memory representation to vend directly out of the borrow/mutate accessors in such case.

For cases where you "are sure" that the payloads do not use that spare bit strategy, I think theoretically it can work through some unsafe casting or accessing pointers to the self/enum, but I recall running into practical issues, especially because the generalized support for "pointers/casting of ~Escapable values" is very limited currently.

1 Like