Automatic Mutable Pointer Conversion

Hi S/E,

T'was the night before dub dub... can't wait myself but in the meantime here's a new pitch for y'all.

I tend to work a lot with Unsafe pointers in Swift and there is one rough edge I bump into frequently I feel we could round off in the compiler even if it is a comparatively niche requirement. Hence:

Automatic Mutable Pointer Conversion

During the review process, add the following fields as needed:

Introduction

This proposal adds automatic conversion from mutable unsafe pointers to their immutable counterparts, reducing unnecessary casting when feeding mutable memory to APIs assuming immutable access. This is most common in, but not exclusive to, C-sourced APIs.

Swift-evolution thread: Pitch: Automatic Mutable Pointer Conversion

Motivation

In C, you may pass mutable pointers (specifically, void *, Type *) to calls expecting immutable pointers (const void *, const Type *). This access is safe and conventional as immutable access to a pointer's memory can be safely assumed when you have mutable access. The same reasoning holds true for Swift but no such implicit cast to immutable counterparts exists. Instead, you must explicitly cast mutable unsafe pointer types (UnsafeMutableRawPointer and UnsafeMutablePointer<Type>) to immutable versions (UnsafeRawPointer and UnsafePointer<Type>). This adds unneeded friction when passing mutable pointers to C functions or comparing pointers of differing mutability.

This friction is most commonly encountered when working with C-sourced APIS, where pointer mutability is not always consistent. Consider the following code, which segments lines in a String using the strchr() function, which accepts an immutable pointer as an input and returns a mutable pointer:

print("""
    line one
    line two
    line three

    """.withCString {
    bytes in
    var bytes = bytes // immutable
    var out = [String]()
    while let nextNewline = // mutable
        strchr(bytes, Int32(UInt8(ascii: "\n"))) {
        out.append(String(data:
            Data(bytes: UnsafePointer<Int8>(bytes),
                 count: UnsafePointer<Int8>(
                    nextNewline)-bytes),
                          encoding: .utf8) ?? "")
        bytes = UnsafePointer<Int8>(nextNewline) + 1
    }
    return out
})

In the preceding example, an unfortunate choice on the part of C API mutability requires a cascade of Swift language conversions. While this example slightly pushes the issue — it would be better to convert the pointer once and earlier — the conversions shouldn't really be necessary at all. Safe interaction between the two languages should be fluid, with minimal overhead for what seems to be unnecessary type safety bookkeeping. Swift has a history of allowing language tuning to reduce exactly this kind of friction.

Proposed solution

This proposal introduces a one-direction automatic conversion from mutable raw- and pointee-typed pointers to their immutable counterparts, allowing developers to supply a mutable pointer wherever an immutable pointer is expected as an argument. Consequently, it will also allow mixed mutability in pointer comparisons and pointer arithmetic via their overloaded operators.

Detailed design

We have prepared a small PR on the Swift compiler. This patch adds a "fix-up" applied after type checking using the existing intrinsic pointer-to-pointer conversion. This patch should not slow down type checking, the most commonly cited reservation for conversions. In our initial tests with compiler benchmarks, we've found an 8% improvement in run-time performance when initializing String from Data.

Source compatibility

This change is purely additive and will facilitate writing simpler code that would previously not compile. The change does not invalidate existing code, as tested by running the source compatibility suite.

Effect on ABI stability

Not applicable, this is source level change.

Effect on API resilience

Not applicable, this is source level change.

Alternatives considered

Continuing to have to apply conversions in code.

Acknowledgments

The Swift language.


Unfortunately I've been unable to prove at this stage that adding such a conversion does not slow the compiler down as I can't believe the results of the compiler benchmarks you'll see against the PR. The benchmarks inflate all metrics for Debug builds by 100% and Release benchmarks by 50% which is simply not credible given the scale of the change. Surprisingly, there actually seems to be a run-time benefit to certain String initialisers when the patch is applied,

Anyway, I'll float this now to see if there is support or any reasoned objections why this change wouldn't be a good idea.

Cheers.

9 Likes

Swift does have this feature for function call arguments, implemented with the same logic as inout-to-pointer and array-to-pointer conversions. It doesn't have it for operators, though, which could be useful, or for direct assignments. (You also don't need to repeat the type, but that relies on knowing that there's no initializer to convert between pointers of different types.)

import Foundation

print("""
    line one
    line two
    line three

    """.withCString {
    bytes in
    var bytes = bytes // immutable
    var out = [String]()
    while let nextNewline = // mutable
        strchr(bytes, Int32(UInt8(ascii: "\n"))) {
        out.append(String(data:
            Data(bytes: bytes,
                 count: UnsafePointer(nextNewline)-bytes),
                          encoding: .utf8) ?? "")
        bytes = UnsafePointer(nextNewline) + 1
    }
    return out
})
2 Likes
Terms of Service

Privacy Policy

Cookie Policy