Property Wrappers in Pattern Matching

While working with low-level code for a sort of linked list, I found myself accessing .pointee a lot. Enough that I wanted an abbreviation for it. Since prefix func * wouldn't work the way it does in C (it would be read-only), I wrote a propertyWrapper instead:

@propertyWrapper struct PointerWrapped<T> {
  var wrappedValue: UnsafeMutablePointer<T>

  var projectedValue: T {
    _read {
      yield wrappedValue.pointee
    }
    nonmutating set {
      wrappedValue.pointee = newValue
    }
    nonmutating _modify {
      yield &wrappedValue.pointee
    }
  }
}

However, I then quickly found myself wanting to do this:

if @PointerWrapped var next = $node.next {
  // ...
}

To my surprise, this doesn't even parse, and gives a rather generic error message ("expected expression, var, or let in 'if' condition"). It's fairly trivial to work around:

if let next = $node.next {
  @PointerWrapped var next = next
  // ...
}

But I'm surprised that I have to.

Why isn't this permitted? If it can be implemented as a fairly trivial transformation in the frontend, is there a deeper reason to discourage this in general? Or is it just a matter of "no use case was presented"?

I'll note that while the transformation is trivial for if let and while let, it's more involved for guard let, but only due to a frontend restriction in variable naming:

guard let next = $node.next else { return }
@PointerWrapped var next = next // invalid redeclaration of next
Aside: I strongly encourage ptr[] as your shorthand (but your idea is still valid)
In defense of property wrappers

The other reason I wanted to write a property wrapper is because it doesn't store absolute pointers, it stores offsets, so I can say

extension PointerWrapped
where T == Node {
  var next: UnsafeMutablePointer<Node>? {
    get {
      if projectedValue.offset == 0 {
        return nil
      } else {
        // Not sure if this is strictly necessary, but `offset` is in bytes, not Node-sizes
        return (UnsafeMutableRawPointer(wrappedValue) + projectedValue.offset)
          .assumingMemoryBound(to: Node.self)
      }
    }
    nonmutating set {
      if let newValue {
        projectedValue.offset = UnsafeRawPointer(newValue) - UnsafeRawPointer(wrappedValue)
      } else {
        projectedValue.offset = 0
      }
    }
  }
}

And then I have node (the pointer), $node.value, and _node.next. This just makes the property wrapper more necessary, tbh.

1 Like