Skip to content

Add generic Dart initializer hook for Rust declarations #3081

@fzyzcjy

Description

@fzyzcjy

Background

While implementing the Rust-to-Dart logging bridge in #3079, we currently need the Dart side of the bridge to be initialized automatically from RustLib.init().

The current implementation achieves this by adding logging-specific codegen logic:

  • The Rust macro expands to functions named frb_init_logger, frb_logging_max_level, and frb_logging_setup_dart_logging_output.
  • Dart codegen scans MIR functions for these exact names.
  • If frb_init_logger exists, codegen injects a FrbDartLogging.init(...) call into generated executeRustInitializers().

This works, but it is too feature-specific. The code generator should not need to know that a particular function name means "initialize logging on the Dart side".

Desired Feature

Add a generic way for Rust-side declarations/macros to request Dart initialization code that is inserted into the generated RustLib.init() flow.

Possible syntax:

#[frb(init_dart_code = r#"
FrbDartLogging.init(
  rustLogStream: api.frbInitLogger(maxLevel: api.frbLoggingMaxLevel()),
  mapRecord: (record) => FrbLogRecordData(
    level: record.level,
    message: record.message,
    target: record.target,
    modulePath: record.modulePath,
    file: record.file,
    line: record.line,
  ),
  setupDefaultOutput: api.frbLoggingSetupDartLoggingOutput(),
);
"#)]
pub fn frb_init_logger(...) -> ... {
    ...
}

Name bikeshedding is welcome. Other possible names:

  • dart_init_code
  • init_dart_code
  • dart_initializer

The important part is the capability, not the exact spelling.

Expected Semantics

  1. The attribute can be attached to a Rust item that is already visible to FRB codegen, probably a function first.
  2. Codegen records the Dart snippet during parsing.
  3. Generated executeRustInitializers() includes these snippets after the existing Rust initializers.
  4. The snippet is emitted into generated Dart in a context where api is available, just like the current logging-specific generated code.
  5. The snippet may reference generated Dart API methods by their Dart names, e.g. api.frbInitLogger(...).
  6. Multiple snippets should be supported and emitted deterministically, ideally in source/MIR order.

The generated shape should continue to look roughly like:

@override
Future<void> executeRustInitializers() async {
  await api.someRustInitializer();
  // generic Dart initializer snippets here
}

Why This Is Needed

The logging bridge is not the only plausible feature that may need this pattern. Any Rust-side macro that expands to FRB APIs and needs a Dart runtime helper could benefit from a generic Dart initializer hook.

Without this feature, each such capability would need codegen to hard-code specific generated function names, which is brittle and does not scale.

Current Code To Replace

The current logging-specific code lives around:

  • frb_codegen/src/library/codegen/generator/wire/dart/spec_generator/misc/mod.rs
    • generate_execute_dart_initializers
    • generate_execute_dart_logging_initializer
    • DartLoggingInitializerFunctionNames

The goal is to remove or greatly reduce logging-specific detection from this layer.

After this feature exists, the logging macro should declare its Dart initializer through the new generic mechanism instead of relying on codegen scanning for frb_init_logger.

Suggested Implementation Plan

  1. Extend #[frb(...)] attribute parsing to accept the new field.

    • Relevant parser code is likely under frb_codegen/src/library/codegen/parser/mir/parser/attribute.rs.
    • Existing dart_code support is a useful reference, but note that current dart_code is class/type extra Dart code, not init-time code.
  2. Add a place in MIR/HIR to store Dart initializer snippets.

    • It may live on MirFunc if attached to functions.
    • Or it may be collected at pack level if that fits existing architecture better.
  3. Update Dart wire codegen.

    • executeRustInitializers() already has a generated section for Rust initializers.
    • Add the collected Dart initializer snippets there.
    • Keep ordering deterministic.
  4. Migrate the logging bridge to use the new attribute.

    • Remove the logging-specific function-name scan from codegen.
    • Keep the generated Dart output behavior equivalent.
  5. Add tests.

Acceptance Criteria

  • A Rust function can be annotated with the new Dart initializer attribute.
  • The generated executeRustInitializers() contains the supplied Dart code.
  • The Dart initializer has access to api.
  • Multiple Dart initializer snippets are emitted in deterministic order.
  • The current Rust-to-Dart logging bridge no longer depends on codegen scanning for frb_init_logger.
  • Existing #[frb(init)] Rust initializer behavior remains unchanged.
  • Existing #[frb(dart_code = "...")] behavior remains unchanged.

Tests To Add Or Update

At minimum:

  • Attribute parser unit test for the new syntax.
  • MIR/parser test showing the initializer snippet is captured.
  • Dart wire generator test showing the snippet appears in executeRustInitializers().
  • Regression test for logging initializer generation after migrating logging to the generic hook.

Useful existing tests to inspect:

  • frb_codegen/src/library/codegen/parser/mir/parser/attribute.rs
  • frb_codegen/src/library/codegen/generator/wire/dart/spec_generator/misc/mod.rs

Notes

This feature is motivated by the logging bridge, but should be implemented as a general codegen capability. Avoid naming it around logging.

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