Skip to content

@_cdecl functions should be declared public #327

@SirVer

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!

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