0

I'm trying to write an extension to Data that allows me to extract it in various casts.

I'm running into a strange problem that I can't quite figure out.

Before I get into generics, I'm trying out fixed data types, and I created this method:

func intValueFromData(_ inData: Data) -> Int64? {
    var number = Int64(0)
    let len = Swift.min(MemoryLayout<Int64>.size, inData.count)
    _ = withUnsafeMutableBytes(of: &number) {
        inData.copyBytes(to: $0, from: 0..<len)
    }

    return number
}

That works. If I do this:

var int_64 = Int64(12345)

var data = Data(bytes: &int_64, count: MemoryLayout<Int64>.size)

let fetched = intValueFromData(data)

fetched becomes "12345" as an Int64.

However, when I try to embed the same method into the Data type, like so:

extension Data {
    mutating func intValueFromData() -> Int64? {
        var number = Int64(0)
        let len = Swift.min(MemoryLayout<Int64>.size, self.count)
        _ = withUnsafeMutableBytes(of: &number) {
            self.copyBytes(to: $0, from: 0..<len)
        }

        return number
    }
}

I get a compile-time error that says that "withUnsafeMutableBytes(of: &number)" is not supposed to have an argument.

The last time that I encountered something like this, it turned out that Apple explicitly blocked a functionality, but neglected to tell us in a straightforward manner.

I am not an expert at this kind of thing, but I am wondering if anyone could shed any light on why withUnsafeMutableBytes(of: &number) behaves differently inside the extension.

Chris Marshall
  • 4,044
  • 6
  • 39
  • 56

2 Answers2

1

In the context of Data, withUnsafeMutableBytes refers to Data.withUnsafeMutableBytes(_:).

To disambiguate this and refer to the global function, explicitly prefix the module name: _ = Swift.withUnsafeMutableBytes(of: &number) {

Alexander
  • 48,074
  • 8
  • 78
  • 121
  • Thanks. I figured that out. I had deleted the question, but I'll undelete in order to give you your greencheck. Thanks! – Chris Marshall Apr 21 '20 at 18:36
  • 1
    @ChrisMarshall You shouldn't delete questions just because you found the answer. Answer it yourself, so others can learn from you in the future. – Alexander Apr 21 '20 at 18:41
  • Yeah...I'm a bit gunshy. I've been dinged for asking questions with obvious answers. I try to research each question as well as possible, but I'll admit to getting a bit steamed when someone tells me I'm...not so bright...because I missed an obvious point. Otherwise, I have no problem being wrong. I'm wrong all the time. That's how I become right. – Chris Marshall Apr 21 '20 at 18:49
  • BTW: I used this to create a pretty darn cool Data extension with a generic extractor. I'm writing the unit tests for it now, and will be adding it to my generic swift toolbox. – Chris Marshall Apr 21 '20 at 18:52
  • @ChrisMarshall I would just be *really* careful with that, because the moment there's a pointer involved, you open up a whole security can of worms – Alexander Apr 21 '20 at 19:22
  • Good point. I *think* it is pretty safe, but, as you mention, there could be security issues. I'll post the method as another answer, and anyone that wants can take pokes at it. Peer review FTW. – Chris Marshall Apr 21 '20 at 19:47
0

Just to complete the set, here's the generic function I created:

/* ###################################################################################################################################### */
// MARK: - Data Extension -
/* ###################################################################################################################################### */
/**
 This extension adds the ability to extract data fron a Data instance, cast into various types.
 */
public extension Data {
    /* ################################################################## */
    /**
     This method allows a Data instance to be cast into various standard types.

     - parameter inValue: This is an inout parameter, and the type will be used to determine the cast.
     - returns: the cast value (the parameter will also be set to the cast value). Can be ignored.
     */
    @discardableResult
    mutating func castInto<T>(_ inValue: inout T) -> T {
        // Makes sure that we don't try to read past the end of the data.
        let len = Swift.min(MemoryLayout<T>.size, self.count)
        _ = Swift.withUnsafeMutableBytes(of: &inValue) {
            self.copyBytes(to: $0, from: 0..<len)
        }

        return inValue
    }
}
Chris Marshall
  • 4,044
  • 6
  • 39
  • 56
  • Just so folks know, this is part of [an open-source (MIT) utility](https://github.com/RiftValleySoftware/RVS_Generic_Swift_Toolbox) – Chris Marshall Apr 21 '20 at 19:50
  • 1
    This overwrites by the bytes of `inValue`, without running a deinitializer. If `inValue` is a non-trivial type, this will leak memory (because its properties' reference counts would not have been deincremented on deinit) or cause other issues. – Alexander Apr 21 '20 at 19:57
  • That's a good point. I really appreciate the feedback. I will see what I can do to mitigate that. I may have to restrict the types in the generic. – Chris Marshall Apr 21 '20 at 20:01
  • Swift doesn't have a way to express "trivial types only" in a generic constraint, as far as I'm aware. I think a better way to do this is by creating the value directly, rather than overwriting some existing value. – Alexander Apr 21 '20 at 20:04
  • Yeah, that's what I'm seeing. I'm not sure how to do that without using explicit types (no more generic). I'll admit to not being a whiz with generics, though. – Chris Marshall Apr 21 '20 at 20:06
  • I'm likely to leave it in there as is, with a warning in the docs. I have a specific use case (Bluetooth Characteristic reading) that it's ideal for. I might be able to slap a precondition in there, so it pukes if someone tries to use a type other than the basics. – Chris Marshall Apr 21 '20 at 20:10
  • No, it doesn't puke, and that's the problem. If it was a compile time error, that would be fine. But it's not, it's a memory leak (that possibly also leaks other things, since people use RAII wrappers in Swift to model the lifetime of other resources like database connections, semaphores, file handles, network ports,etc.) I strongly suggest you change it. – Alexander Apr 21 '20 at 20:12
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/212201/discussion-between-alexander-reinstate-monica-and-chris-marshall). – Alexander Apr 21 '20 at 20:13
  • I did want to give a H/T to @Alexander-ReinstateMonica, as he was able to convince me that the hack (which is what it really is) did not belong in a publicly-released utility. I am quite used to strong pushback, having worked for Nikon for 27 years, and amongst some of the finest scientists and engineers in the world. I have strong opinions, and I'm smarter than the average bear, but I am often wrong, and learning that I'm wrong is one way to become right. That's one of the reasons that I like this place. – Chris Marshall Apr 26 '20 at 11:35