Difficulty using MutableSpan

I’m trying to use MutableSpan in a project for the first time. I’m having difficulty with a couple things:

  1. Is it intentional that there’s no UnsafeMutableRawBufferPointer.mutableSpan? I expected such a property to exist and return a MutableRawSpan. As a workaround, I am using UnsafeMutableBufferPointer<UInt8> for my read buffers, and passing around MutableSpan<UInt8>s.

  2. When I try to pass a borrowed MutableRawSpan to a function, I cannot assign to it:

let buf = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: 1)
doStuff(with: buf.mutableSpan)

func doStuff(with span: borrowing MutableSpan<UInt8>) {
    span[0] = 42
    // error: cannot assign through subscript: 'span' is a 'let' constant
}

…but if I make MutableSpan an inout, the error moves to the call site:

let buf = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: 1)
doStuff(with: &buf.mutableSpan)
// cannot pass immutable value as inout argument: 'mutableSpan' is a get-only property

func doStuff(with span: inout MutableSpan<UInt8>) {
    span[0] = 42
}

I can’t even work around this by using the .mutableBytes property, because it is declared mutating get:

let buf = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: 1)
doStuff(with: buf.mutableSpan)

func doStuff(with span: borrowing MutableSpan<UInt8>) {
    span.mutableBytes.storeBytes(of: 42, as: UInt8.self)
    // error: cannot use mutating getter on immutable value: 'span' is a 'let' constant
    // error: cannot use mutating member on immutable value: 'mutableBytes' is a get-only property
}

What is the correct pattern here? I’m on Swift 6.2.3.

1 Like

For 1), I’m not sure.

For 2), the go-to way you likely want to write functions which take a MutableSpan is as consuming.

The following snippet should work as intended.

let buf = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: 1)
doStuff(with: buf.mutableSpan)

func doStuff(with span: consuming MutableSpan<UInt8>) {
    span[0] = 42
}

Further less-important elaboration :

Passing it as borrowing intentionally doesn’t let you mutate since borrowing is for immutable.

The issue you’re hitting with trying to pass as inout is a frequent difficulty I’ve hit as well. The reason why it doesn’t work is as the error message says, which is that the expression but.mutableSpan kind of returns an “immutable mutable span”, in the same way that in general in swift, owned values returned from functions are immutable unless you explicitly assign them to a var. This rule ends up feeling a bit awkward though for non-escapable types under normal consideration because it’s an “owned value which represents an ongoing access into another value”. So to make the inout one work, you’d have to formally assign it as a mutable var:

let buf = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: 1)
var mutableSpan = buf.mutableSpan
doStuff(with: &mutableSpan)

func doStuff(with span: inout MutableSpan<UInt8>) {
    span[0] = 42
}

That one will work, but I’m only noting it for completeness. The correct way IMO is to use consuming as I mentioned initially. A followup question you may run into is, “what if I have a situation like the following?”:

func doStuffOne(with span: consuming MutableSpan<UInt8>) {
  // I need to first call `doStuffTwo`, then do further mutations
  doStuffTwo(span)
  
  span[0] = 100 // Error: Using `span` after it has been consumed
}

func doStuffTwo(with span: consuming MutableSpan<UInt8>) {
  ...
}

And the solution for such case is:

func doStuffOne(with span: consuming MutableSpan<UInt8>) {
  // I need to first call `doStuffTwo`, then do further mutations
  doStuffTwo(span.extracting(...)) 
  
  span[0] = 100 
}

There are language features which could allow expressing this sort of thing more naturally. Specifically an exclusive modifier `exclusive` parameter ownership modifier - #9 by John_McCall . Or the idea of supporting “reborrowing” as Rust does it. I think you can get pretty far with just consuming though, not a blocker.

5 Likes

I think, in order to prove to the compiler that you have exclusive access to the UnsafeMutableBufferPointer, that you need to declare buf with var instead of let. Which is odd for a reference type, but I think it's required for the lifetime checking that Span needs to be usable in safe code. I think this is the main motivation for the exclusive ownership pitch.

Edit: Actuaily, no. It works if you just make the span paramater to doStuff consuming. Making buf a var does not help. I now have no idea how this works. SwiftFiddle - Swift Online Playground

1 Like

Ah, now I vaguely recall this coming up during the {Mutable}Span discussions. (I haven’t had a chance to actively use the feature since it was pitched and implemented.) Specifically, I recall being confused by Atomic is always let and mutable, but MutableSpan is immutable unless var. As you said, it really comes down to the matter of exclusive ownership, which the language has historically conflated with mutability.

Yes, this is very similar to the actual problem I want to solve.

Hm, all the of the extracting methods on MutableSpan (and MutableRawSpan) are deprecated: MutableSpan | Apple Developer Documentation

Ah right, I believe that’s because there’s some open questions on how mutating vs consuming variants will be provided. The non-deprecated version that is there is span._mutatingExtracting(…) swift/stdlib/public/core/Span/MutableSpan.swift at main · swiftlang/swift · GitHub, though I’m not sure how long-term stable that is (some equivalent functionality should be offered if it were to be deprecated I’m sure).

The mutating one is for “A new span over the same data which counts as a mutation of my original span, leaving the original span in-tact and usable once I’m done with the new instance” (which is what is needed in this case).

The consuming one if for the case of “Consume the original one and transfer its lifetime to the new one”. This one is needed in cases where you want the newly extracted span to outlive the original span