Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert 'void *' return from a C function to 'UnsafeMutableRawPointer' in Swift 3?

I'm trying to convert a lua bridge from Swift 2 to Swift 3. I am not the original author so there are aspects of the library I don't know very well and the original author seems not interested to continue working on the project. I have most of the conversion done but there remain one place I'm stuck and could not figure out. I've tried searching on SO and on the Internet but could not find anything that could help me solve the problem.

If anyone is interested in looking at the full source code, here is my fork of the project on github: https://github.com/weyhan/lua4swift (My changes is in the Swift3 branch)

Allow me setup the context to the error I'm stuck on. There is a Userdata class, specifically in the method userdataPointer<T>() -> UnsafeMutablePointer<T> the c function lua_touserdata returns the block address of userdata as a void * pointer type.

Original code written in Swift 2:

public class Userdata: StoredValue {

    public func userdataPointer<T>() -> UnsafeMutablePointer<T> {
        push(vm)
        let ptr = lua_touserdata(vm.vm, -1)
        vm.pop()
        return UnsafeMutablePointer<T>(ptr)
    }

    public func toCustomType<T: CustomTypeInstance>() -> T {
        return userdataPointer().memory
    }

    public func toAny() -> Any {
        return userdataPointer().memory
    }

    override public func kind() -> Kind { return .Userdata }

}

After the conversion with Xcode 8 migration tool, Xcode is complaining about the return line with error Cannot invoke initializer for type 'UnsafeMutablePointer<T>' with an argument list of type '(UnsafeMutableRawPointer?)':

return UnsafeMutablePointer<T>(ptr)

I've fixed it with:

return (ptr?.assumingMemoryBound(to: T.self))!

Following that above change, now Xcode 8 is now complaining about the calling statement in createCustomType:

public func createCustomType<T: CustomTypeInstance>(setup: (CustomType<T>) -> Void) -> CustomType<T> {
    lua_createtable(vm, 0, 0)
    let lib = CustomType<T>(self)
    pop()

    setup(lib)

    registry[T.luaTypeName()] = lib
    lib.becomeMetatableFor(lib)
    lib["__index"] = lib
    lib["__name"] = T.luaTypeName()

    let gc = lib.gc
    lib["__gc"] = createFunction([CustomType<T>.arg]) { args in
        let ud = args.userdata

        // ******* Here's the line that is causing problem in Swift 3
        (ud.userdataPointer() as UnsafeMutablePointer<Void>).destroy()
        // *******

        let o: T = ud.toCustomType()
        gc?(o)
        return .Nothing
    }

    if let eq = lib.eq {
        lib["__eq"] = createFunction([CustomType<T>.arg, CustomType<T>.arg]) { args in
            let a: T = args.customType()
            let b: T = args.customType()
            return .Value(eq(a, b))
        }
    }
    return lib
}

Where I'm getting stuck is the line :

(ud.userdataPointer() as UnsafeMutablePointer<Void>).destroy()

I believe the original author is attempting to clear the memory block where the pointer returned by userdataPointer() call is pointing to.

With the Xcode 8 auto migration tool the above line is converted as below:

(ud.userdataPointer() as UnsafeMutableRawPointer).deinitialize()

However Xcode now is then complains that Cannot convert call result type 'UnsafeMutablePointer<_>' to expected type 'UnsafeMutableRawPointer'.

From my research, the change to the return line in userdataPointer seems correct, so I think the issue is with the cast to UnsafeMutableRawPointer. I've tried dropping the cast to UnsafeMutableRawPointer and invoke ud.userdataPointer().deinitialize() directly but I get this error Generic parameter 'T' could not be inferred.

Other things I've tried is to convert the UnsafeMutablePointer to UnsafeMutableRawPointer but It always result in Xcode 8 complaining one thing or another. Any suggestion on how to get this to work?

like image 408
WeyHan Ng Avatar asked Oct 05 '16 11:10

WeyHan Ng


2 Answers

As you may have already found out, Swift 3 attempts to provide better type safety when it comes to pointers. UnsafeMutablePointer can now only represent a pointer to an instance of a known type. In Swift 2, a C void * was represented by UnsafeMutablePointer<Void>, allowing void and non-void pointers to be treated in the same way, including trying to call a de-initializer of the pointed-to type, which is what the destroy() method in the problematic line of code does:

  (ud.userdataPointer() as UnsafeMutablePointer<Void>).destroy()

In Swift 3 the de-initializer on the pointee is called using the deinitialize() method of the UnsafeMutablePointer structure. It appears that the migration assistant got confused. The line

(ud.userdataPointer() as UnsafeMutableRawPointer).deinitialize()

makes little sense because (1) UnsafeMutablePointer cannot be converted using as to UnsafeMutableRawPointer; (2) UnsafeMutableRawPointer has not deinitialize() method. In Swift 3, UnsafeMutableRawPointer is a special type to represent void*. It is actually quite understandable why the migration tool made this mistake: it blindly replaced destroy() with deinitialize() and UnsafeMutablePointer<Void> with the corresponding Swift 3 type UnsafeMutableRawPointer, without realizing that the conversion would not work.

I don't quite understand why calling destroy() on a void pointer would work in Swift 2. Maybe this was a bug in the program or some compiler trick allowed the correct de-initializer to be called. Without knowing enough about the code, I can't be more specific than to suggest analyzing it to figure out what is the type pointed to by that pointer on which destroy() was called. For example, if we know for sure that it is always the placeholder type T used on the following line:

   let o: T = ud.toCustomType()

then the offending line simply becomes

  (ud.userdataPointer() as UnsafeMutablePointer<T>).deinitialize()

We need the conversion in the parentheses to allow the compiler to infer the generic parameter.

Thank you for bringing up an interesting problem. BTW, once you get over this obstacle, you are likely to run into other problems. One thing that jumps out is that UnsafeMutablePointer has no .memory in Swift 3; you'll have to use .pointee instead.

Here's an update. After playing with Swift 2.2 on Linux, I realize that calling destroy() on an UnsafeMutablePointer<A> cast as UnsafeMutablePointer<Void> won't call A's deinitializer, even if it has one. So, you have to be careful with that line...

like image 160
Anatoli P Avatar answered Oct 07 '22 15:10

Anatoli P


Try creating an instance of UnsafeMutableRawPointer instead of trying to cast it:

UnsafeMutableRawPointer<T>(ud.userdataPointer()).destroy()

like image 33
mluisbrown Avatar answered Oct 07 '22 13:10

mluisbrown