Skip to content

@_cdecl functions should be declared public #327

Description

@SirVer

I ran into a subtle bug that took me a while to track down. I presume the fix is not too hard, but I have not looked deep.

I have the setup where I call from Swift into Rust, but I want to hand a callback into Swift to rust. The pattern I choose for this looks like this:

#[swift_bridge::bridge]
mod ffi {
    extern "Swift" {
        type Observer;
        fn on_update(&self, path: &str);
    }

    extern "Rust" {
        type Actor;
        fn do_some_stuff(&mut self, observer: Observer);
    }
}

// some rust code implementing Actor and calling `on_update`

I create a swift package after code gen using swift-bridge-cli create-package; I then drop a tiny .swift file into the Sources dir that contains a dummy implementation of Observer. My real code depends on this Swift Package as a local dependency.

This worked wonderfully for Debug builds, however for Release builds I got linker errors, __swift_bridge__$Observer$on_update and __swift_bridge__$Observer$free could not be found. I tracked down that toggling ENABLE_TESTABILITY=YES made the code compile again. After some ChatGPTing I came to the understanding that without this, the symbols are stripped from the compiler. The solution was pretty simple: just go over the generated code and make all functions tagged with @_cdecl public.

As a workaround I am currently using this python script.

#!/usr/bin/env python3

"""
add_public_to_swift_bridge.py  <swift‑bridge file>

For every line that starts with  “@_cdecl”   the script inserts
“public ” at the beginning of the very next line, preserving whatever
indentation was already there.  It edits the file in‑place.
"""

import re
import sys
from pathlib import Path


def main(path: Path) -> None:
    text = path.read_text()
    lines = text.splitlines(keepends=True)
    for i, line in enumerate(lines[:-1]):
        if re.match(r"^@_cdecl", line):
            if not lines[i + 1].lstrip().startswith("public "):
                lines[i + 1] = f"public " + lines[i + 1]
    path.write_text("".join(lines))


if __name__ == "__main__":
    if len(sys.argv) != 2:
        sys.exit(
            "usage: add_public_to_swift_bridge.py  path/to/taskpaper_bridge.swift"
        )
    main(Path(sys.argv[1]))

Thank you so much for this software - it is a lot of fun to use and very useful indeed!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions