The Swift Evolution proposal for BitwiseCopyable states:
Developers cannot conform types defined in other modules to the protocol.
I understand the dangers of assuming that something is bitwise-copyable when it isn't, but it's something we're forced to do on the regular when interacting with C code.
Is there a secret incantation that can let me annotate other libraries' C structures as BitwiseCopyable?
Here's an example C API I'm trying to write a wrapper for, that would need thi
import Darwin
public func getValue<ValueType>(ofType prototype: ValueType, for keys: [CInt]) throws -> ValueType {
var keys = keys
var valueOnStack = prototype
var sizeOfValueOnStack = MemoryLayout<ValueType>.size
let status = Darwin.sysctl(
&keys,
UInt32(keys.count),
/* oldp */ &valueOnStack, /* oldlenp */ &sizeOfValueOnStack,
// ⚠️ `- warning: forming 'UnsafeMutableRawPointer' to a variable of type 'ValueType';
// this is likely incorrect because 'ValueType' may contain an object reference.
/* newp */ nil, /* newlenp */ 0
)
guard status == 0 else {
fatalError("Sysctl failed with errno: \(errno) (\"\(String(cString: strerror(errno)))\")")
}
assert(sizeOfValueOnStack == MemoryLayout<ValueType>.size, """
The size of the value returned (\(sizeOfValueOnStack) bytes) should match the size of \
the value's type (\(MemoryLayout<ValueType>.size) bytes).
""")
return valueOnStack
}
let coreCount = try! getValue(ofType: CInt(), for: [CTL_HW, HW_NCPU])
print("This machine has \(coreCount) CPU cores.")
The warning is correct in general, but this API would only ever be used with bitwise-copyable values in practice. I can silence the warning by correctly constraining the generic type ValueType: BitwiseCopyable.
This function is susceptible to stack smashing if the given type is incorrect for the given key, so it takes some care to use correctly. I don't see much of a way around that. I could use a malloc'ed buffer to write into, but risk of overrunning that buffer is much the same. In any case, the warning regarding bitwise-copyability is the same.
This raises a new issue: some of sysctl APIs returns C structures, like kinfo_proc, which aren't marked BitwiseCopyable:
let infoAboutThisProcess = try! getValue(ofType: kinfo_proc(), for: [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()])
// ❌ global function 'getValue(ofType:for:)' requires that 'kinfo_proc' conform to 'BitwiseCopyable'
print(infoAboutThisProcess)
I can try to add the conformance myself:
extension kinfo_proc: @retroactive BitwiseCopyable {}
// ❌ conformance to 'BitwiseCopyable' must occur in the same module as struct 'kinfo_proc'
Shouldn’t all C types be bitwise copyable by default? That’s how C’s going to treat them, anyway. (For C++ this should depend on the trivially_copyable concept or whatever.)
For reasons that aren't immediately coming to me, BitwiseCopyable conformance was added to all fixed-width integer types in the standard library, but making the FixedWidthInteger protocol itself refine BitwiseCopyable was removed from the proposal before review.
It's certainly not intentional, though, that retroactively declaring conformance to FixedWidthInteger provides a backdoor to retroactively make a type that you don't own get treated as though it's bitwise copyable by the diagnostics engine.
The code here that you're pointing to here diagnosing implicit conversion from a nontrivial inout type to a raw pointer pre-dates BitwiseCopyable and hardcodes FixedWidthInteger because (I presume) it was a convenient way to stage in diagnostics for a large chunk of trivial types without the real feature. It could—and I think maybe should—probably be stripped out now in favor of the now-added immediately preceding test for actual conformance to BitwiseCopyable (cc @Slava_Pestov).
What are other more reasonable workarounds here? Should I just bite the bullet and use an allocation? (I do a lot of these calls in my app, doing several calls for every running PID... per second )
working demo
public func getValue<ValueType>(_: ValueType.Type, for keys: [CInt]) throws -> ValueType {
var keys = keys
var size = MemoryLayout<ValueType>.size
let value = UnsafeMutableRawPointer.allocate(byteCount: size, alignment: MemoryLayout<ValueType>.alignment)
let status = Darwin.sysctl(
&keys,
UInt32(keys.count),
value, &size,
nil, 0
)
guard status == 0 else {
fatalError("Sysctl failed with errno: \(errno) (\"\(String(cString: strerror(errno)))\")")
}
assert(size == MemoryLayout<ValueType>.size, """
The size of the value returned (\(size) bytes) should match the size of \
the value's type (\(MemoryLayout<ValueType>.size) bytes).
""")
return value.bindMemory(to: ValueType.self, capacity: 1).pointee
}
let coreCount = try! getValue(CInt.self, for: [CTL_HW, HW_NCPU])
print("This machine has \(coreCount) CPU cores.")
let cpuType = try! getValue(cpu_type_t.self, for: [CTL_HW, HW_NCPU])
let infoAboutThisProcess = try! getValue(kinfo_proc.self, for: [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()])
print(infoAboutThisProcess)
IIRC, it's because there's nothing in the semantics of FixedWidthInteger that requires it to be a bitwise copyable type. It would likely be silly, but someone could write a conforming type that implements it correctly but uses some storage that isn't trivially copyable.
An overlay is a Swift module that shares the same name as and re-exports an underlying C module. That's why the conformance is considered safe (and not technically retroactive)—it's occurring in the "same" module that defined the C type. Apple's platform SDKs already define their own Swift overlay for Darwin, so it would definitely be A Bad Idea™ to try to wedge your own in instead. (Well, it would be a bad idea to do that for any module you don't own, whether there's already an overlay or not.)
The same inference will be done on imported C and C++ types.
For an imported C or C++ enum, the compiler will always generate a conformance to BitwiseCopyable.
For an imported C struct, if all its fields are BitwiseCopyable, the compiler will generate a conformance to BitwiseCopyable. The same is true for an imported C++ struct or class, unless the type is non-trivial.
For an imported C or C++ struct, if any of its fields cannot be represented in Swift, the compiler will not generate a conformance. This can be overridden, however, by annotating the type __attribute__((__swift_attr__("BitwiseCopyable"))).
Make sense in principle, but the current rules baked into the compiler do imply that FixedWidthInteger: BitwiseCopyable in practice. I could get behind removing that rule like xwu suggested.
Is this something I could do by re-declaring the kinfo_proc in a .h header file in my own project? Would Swift consider that as coming from a different module?
Scoping of a C type within its imported module is, for lack of a better word, "weird", so it's not really a good idea to define a type with the same name in more than one module. Even if the types have an identical definition, since you'll certainly be importing Darwin as well as your custom module, you might get one type or the other depending on how the name resolves in different places. And they might not even be treated as the same type, leading to bizarre errors like cannot convert 'kinfo_proc' to 'kinfo_proc'.
If I try to use the BitwiseCopyable conformance of a randomly chosen C type, it works, so this appears to be specific to something in the definition of kinfo_proc:
1> import Darwin
2> func f<T: BitwiseCopyable>(_ t: T) {}
3> f(sockaddr())
// ^ OK
4> f(kinfo_proc())
repl.swift:2:6: note: where 'T' = 'kinfo_proc'
func f<T: BitwiseCopyable>(_ t: T) {}
^
error: repl.swift:4:1: global function 'f' requires that 'kinfo_proc' conform to 'BitwiseCopyable'
f(kinfo_proc())
^
repl.swift:2:6: note: where 'T' = 'kinfo_proc'
func f<T: BitwiseCopyable>(_ t: T) {}
^
But kinfo_proc is nested struct madness so I can't even begin to guess what the compiler is seeing somewhere in it that makes it decide not to infer the conformance.
For an imported C or C++ struct, if any of its fields cannot be represented in Swift, the compiler will not generate a conformance. This can be overridden, however, by annotating the type __attribute__((__swift_attr__("BitwiseCopyable"))).
unions are examples fields that can't be represented in Swift. I suspect VLA members and opaque structs are in the same boat, though I haven't tested.
So I think this is behaving as the proposal intended, but I don't think the proposal's restrictions are appropriate. It seems totally reasonable to me that C unions should be inferred as BitwiseCopyable, so long as all their variant types are BitwiseCopyable. In practice, since all other pure C types are BitwiseCopyable, that would mean that all pure C unions would be too, C++ unions would be BitwiseCopyable IFF they're trivial (according to its own language rules, i.e. contain no non-trivial members).
Would proposing a small change like that require an SE proposal?
Unions are imported into Swift just fine as a struct that offers initializers and properties for each variant. But maybe that synthetic type isn’t getting an appropriate BitwiseCopyable conformance?
Ah yeah, I've combed through that, but the only BitwiseCopyable-related code I found was this, which is only for handling the __attribute__((__swift_attr__("BitwiseCopyable"))) attribute.
I've gone through some of PRs that implemented this feature and found KnownProtocolKind::BitwiseCopyable was a promising search term, which lead me to BitwiseCopyableStorageVisitor which seemed highly relevant. The forEachMissingConformance got too complex for me to trace through manually; I'll need to figure out how to build+run+debug the Swift compiler and step through it properly.