2

I am working on a C++ library with a Swift backend (the C++ code mainly serves as a bridge to Rust).

I have the following:

char* get_string(void* pBackend) {
    Backend* backend = (Backend*) pBackend;
    return backend.get_string ???; // or does this need more processing?
}

In turn Rust will call get_string from the cpp library, but never mutate it.

The question is how to properly handle this from the Swift side, I have lost count of all the numerous pointer swift and cpp types.

in Swift:

struct Backend {
  var myString: String? = nil
  func getString() -> ??? { // which type to return?
    if let s = myString {
      return ??? // how to get it?
    } else {
      "no String"
    }
  }
}

I know there may be some lifetime concerns, but as soon as Rust call get_string, it copies the contents.

(The backend has more internal functions that will actually set myString.)

1

2 Answers 2

7

It is possible to just return a Swift String on the Swift side. Swift Strings can be easily converted to std::strings. See also the documentation.

// Make these public so that they can be accessed from C++
public struct Backend {
    var myString: String? = nil
    
    public func getString() -> String {
        return myString ?? "no String"
    }
}

On the C++ side, you can do:

char* get_string(void* pBackend) {
    Backend* backend = (Backend*) pBackend;
    swift::String stringFromSwift = backend->getString();
    std::string cppString = (std::string)stringFromSwift;
    return strdup(cppString.c_str());
}

Since the string needs to outlive get_string, it needs to be copied to the heap. The caller of get_string is responsible for freeing this.

Sign up to request clarification or add additional context in comments.

3 Comments

Just a shame the returned char* needs to be freed given that the actual swift value will persist in Backend after the call. I do prefer this answer over @ali_nazzal's answer since it works on Backend directly making the swift code cleaner. I am also reluctant to use @_cdecl because Apple is not clear about what the future of this is going to be. Thank you.
I don't use C++ very much lately. Does the C++ string c_str() function return a char* serialization of a C++ string object that supports unicode?
@DuncanC I don't use C++ regularly either. The documentation for c_str() is here. As for Unicode support, this answer explains it quite well IMO.
2

Return a C string with stable storage and a matching free. Don’t return a pointer into Swift’s String.

import Darwin

final class Backend { var myString: String? }

@_cdecl("backend_get_string")
public func backend_get_string(_ p: UnsafeMutableRawPointer?) -> UnsafeMutablePointer<CChar>? {
    guard let p else { return nil }
    let b = Unmanaged<Backend>.fromOpaque(p).takeUnretainedValue()
    let s = b.myString ?? "no String"
    return strdup(s)                 // caller must free
}

@_cdecl("backend_string_free")
public func backend_string_free(_ p: UnsafeMutablePointer<CChar>?) { if let p { free(p) } }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.