brickpop/flutter-rust-ffi

Works on debug, but not on release

Closed this issue · 6 comments

Hello and thanks for the template and article.

I'm trying to use the produced .a file as a static library, I'm including the .h on the bridge header file and everything works on debug on a real-device and emulator.

However when I archive and test from Testflight I get a grey screen, if I comment out all the Rust invocations from Flutter, the App works as expected.
Is there something specific that needs to be done in order for the library to be included? I have it on embedded libraries and frameworks. Not sure what else to do.

Thanks in advance!

Hi @netgfx

Did you make sure to invoke every single one of your rust functions from the swift file?

In the Readme:

NOTE: By default, XCode will skip bundling the libexample.a library if it detects that it is not being used. To force its inclusion, add dummy invocations in SwiftMylibPlugin.swift that use every single native function that you use from Flutter:

...
  public func dummyMethodToEnforceBundling() {
    rust_greeting("...");
    compress_jpeg_file("...");
    compress_png_file("...");
    // ...
    // This code will force the bundler to use these functions, but will never be called
  }
}

Hope this helps.

@brickpop
I have it like this:

import Flutter
import UIKit

public class SwiftApproverRustPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "approver_rust", binaryMessenger: registrar.messenger())
    let instance = SwiftApproverRustPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    result("iOS " + UIDevice.current.systemVersion)
  }
    
    public func dummyMethodToEnforceBundling() {
      
      // This will never be executed
      get_requests()
     
     "!".withCString {
      let opt = UnsafeMutablePointer(mutating: $0)
              approve_request(opt, opt)
      }
      
      "".withCString {
        let opt = UnsafeMutablePointer(mutating: $0)
        // use 'opt'
          approve_request_free(opt)
      }
      
      
      "?".withCString {
           let opt = UnsafeMutablePointer(mutating: $0)
          reject_request(opt)
      }
      
      "-".withCString {
           let opt = UnsafeMutablePointer(mutating: $0)
          reject_request_free(opt)
      }
    }
}

and the .h file:

char *approve_request(const char *id, const char *approval);

void approve_request_free(char *s);

char *get_requests(void);

void get_requests_free(char *s);

char *reject_request(const char *id);

void reject_request_free(char *s);

hm... I think I'm missing get_requests_free dammit... let me try again

Seems that things are somehow worse now, I'm getting:

argument(s): Failed to lookup symbol (dlsym(RTLD_DEFAULT, get_requests): symbol not found)

on debug.

I have also simplified the .swift file to

 public func dummyMethodToEnforceBundling() {
      
      // This will never be executed
      get_requests()
      approve_request("", "")
      approve_request_free("")
      get_requests_free("")
      reject_request("")
      reject_request_free("")
      
    }

I have managed to pull it off... some pitfalls (These are mostly for iOS)

  • Make sure this exists on your plugin .yaml if you created the plugin without a target platform. Where ApproverRustPlugin is the name if your .h file.
flutter:
  plugin:
    pluginClass: ApproverRustPlugin
  • As it has been mentioned include ALL public functions from your .a file into the .swift file (even the free ones)
    (This is a simple way I found to call functions that require an argument)
"".withCString {
        let opt = UnsafeMutablePointer(mutating: $0)
        // use 'opt'
          send_request_free(opt)
      }

When you finish your plugin include it on your main app .yaml file like this (if it exists on the same folder)

dependencies:
  flutter:
    sdk: flutter
  approver_rust:
    path: "./approver_rust"

Then do flutter build ios --release and Archive from XCode.

I'm glad you got it to work.

The best way is to either use the repo as a template or follow the steps described from the article: https://medium.com/flutter-community/finally-running-rust-natively-on-a-flutter-plugin-here-is-how-6f2826eb1735

  • The pluginClass field is automatically taken care of when creating the Flutter plugin
  • The goal of the repo is to provide a Flutter plugin, not a Flutter app, so yes... you'll need to import it from an actual app. The easiest way is by using the example app on the repo itself.

Feel free to submit a PR if you feel that the readme needs some clarification

I believe the greatest shortcoming on my part was that when reading the article and seeing this repo I didn't really know what a flutter plugin was. And I thought that by linking Swift with the .a lib was the same thing, but it isn't because I have learned the hard way that linking it like this (directly from Swift) has terrible performance, wile with FFI things work smoothly.

I'm just stating this in case anyone else comes looking.

Thanks again for the great tools!