{"id":19141485,"url":"https://github.com/bearcove/rubicon","last_synced_at":"2025-04-12T03:49:22.227Z","repository":{"id":248120923,"uuid":"827794352","full_name":"bearcove/rubicon","owner":"bearcove","description":"rubicon enables a form of dynamic linking in Rust through cdylib crates and carefully-enforced invariants.","archived":false,"fork":false,"pushed_at":"2024-12-06T15:22:29.000Z","size":156,"stargazers_count":147,"open_issues_count":3,"forks_count":4,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-04-11T19:58:04.943Z","etag":null,"topics":["dylib","dynamic-linking","linking","rust","rust-lang"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bearcove.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["fasterthanlime"]}},"created_at":"2024-07-12T11:43:39.000Z","updated_at":"2025-04-10T13:42:00.000Z","dependencies_parsed_at":"2024-11-09T07:24:16.996Z","dependency_job_id":"e274e6db-158a-45dd-9a56-92ff1d485220","html_url":"https://github.com/bearcove/rubicon","commit_stats":{"total_commits":140,"total_committers":5,"mean_commits":28.0,"dds":0.1071428571428571,"last_synced_commit":"c5c0cdcd958f700c01b43db2108e183ad430baa1"},"previous_names":["bearcove/rubicon"],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bearcove%2Frubicon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bearcove%2Frubicon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bearcove%2Frubicon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bearcove%2Frubicon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bearcove","download_url":"https://codeload.github.com/bearcove/rubicon/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248473125,"owners_count":21109628,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dylib","dynamic-linking","linking","rust","rust-lang"],"created_at":"2024-11-09T07:23:32.902Z","updated_at":"2025-04-12T03:49:22.208Z","avatar_url":"https://github.com/bearcove.png","language":"Rust","funding_links":["https://github.com/sponsors/fasterthanlime"],"categories":[],"sub_categories":[],"readme":"[![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](LICENSE-MIT)\n[![crates.io](https://img.shields.io/crates/v/rubicon.svg)](https://crates.io/crates/rubicon)\n[![docs.rs](https://docs.rs/rubicon/badge.svg)](https://docs.rs/rubicon)\n[![cursed? yes](https://img.shields.io/badge/cursed%3F-yes-red.svg)](https://github.com/bearcove/rubicon)\n\n# rubicon\n\n![The rubicon logo: a shallow river in northeastern Italy famously crossed by Julius Caesar in 49 BC](https://github.com/user-attachments/assets/7e10888d-9f44-4395-a2ad-3e3fc0801996)\n\n_Logo by [MisiasArt](https://misiasart.com)_\n\nrubicon enables a form of dynamic linking in Rust through cdylib crates\nand carefully-enforced invariants.\n\n## Name\n\nWebster's Dictionary defines 'rubicon' as:\n\n  \u003e a bounding or limiting line. especially: one that\n  \u003e when crossed, commits a person irrevocably.\n\nIn this case, I see it as the limiting line between several shared objects,\nwithin the same address space, each including their own copy of the same Rust\ncode.\n\n## Nomenclature\n\nDynamic linking concepts have different names on different platforms:\n\n| Concept             | Linux               | macOS                    | Windows                                                              |\n|-------------------- | ------------------- | ------------------------ | -------------------------------------------------------------------- |\n| Shared library      | shared object       | dynamic library          | DLL (Dynamic Link Library)                                           |\n| Library file name   | `libfoo.so`         | `libfoo.dylib`           | `foo.dll`                                                            |\n| Library search path | `LD_LIBRARY_PATH`   | `DYLD_LIBRARY_PATH`      | `PATH`                                                               |\n| Preload mechanism   | `LD_PRELOAD`        | `DYLD_INSERT_LIBRARIES`  | [It's complicated](https://stackoverflow.com/a/5273439)              |\n\nThroughout this document, macOS naming conventions are preferred.\n\n## Motivation\n\n### Rust's dynamic linking model (`1graph`)\n\n(This section is up-to-date as of Rust 1.79 / 2024-07-18)\n\ncargo and rustc support some form of dynamic linking, through the\n[-C prefer-dynamic][prefer-dynamic] compiler flag.\n\n[prefer-dynamic]: https://doc.rust-lang.org/rustc/codegen-options/index.html#prefer-dynamic\n\nThis flag will:\n\n  * Link against the pre-built `libstd-HASH.dylib`, shipped via rustup\n    (assuming you're not using `-Z build-std`)\n  * Try to link against `libfoobar.dylib`, for any crate `foobar` that\n    includes `dylib` in its `crate-type`\n\nrustc has an [internal algorithm][] to decide which linkage to use for which\ndependency. That algorithm is best-effort, and it can fail.\n\n[internal algorithm]: https://github.com/rust-lang/rust/blob/master/compiler/rustc_metadata/src/dependency_format.rs\n\nRegardless, it assumes that rustc has knowledge of the entire dependency graph\nat link time.\n\n### rubicon's dynamic linking model (`xgraph`)\n\nHowever, one might want to split the dependency graph on purpose:\n\n| Strategy                     | 1graph (one dependency graph)                | xgraph (multiple dependency graphs)                                 |\n| ---------------------------- | -------------------------------------------- | ------------------------------------------------------------------- |\n| Module crate-type            | dylib                                        | cdylib                                                               |\n| Duplicates in address space  | No (rlib/dylib resolution at link time)      | Yes (by design)                                                      |\n| Who loads modules?           | the runtime linker                           | the app                                                              |\n| When loads modules?          | before main, unconditionally                 | any time (but don't unload)                                          |\n| How loads modules?           | `DT_NEEDED` / `LC_LOAD_DYLIB` etc.           | libdl, likely via [libloading](https://docs.rs/libloading/latest/libloading/) |\n\nLet's call Rust's \"supported\" dynamic linking model \"1graph\".\n\nrubicon enables (at your own risk), a different model, which we'll call \"xgraph\".\n\nIn the \"xgraph\" model, every \"module\" of your application — anything that might make\nsense to build separately, like \"a bunch of tree-sitter grammars\", or \"a whole JavaScript runtime\",\nis its _own_ dependency graph, rooted at a crate with a `crate-type` of `cdylib`.\n\nIn the \"xgraph\" model, your application's \"shared object\" (Linux executables, macOS executables,\netc. are just shared objects — not too different from libraries, except they have an entry point)\ndoes not have any references to its modules — by the time `main()` is executed, none of the\nmodules are loaded yet.\n\nInstead, modules are loaded explicitly through a crate like [libloading](https://lib.rs/crates/libloading),\nwhich under the hood, uses whatever facilities the platform's dynamic linker-loader exposes. This\nlets you choose which modules to load and when.\n\n### Linkage and discipline\n\nThe \"xgraph\" model is dangerous — we must use discipline to get it to work at all.\n\nIn particular, we'll maintain the following invariants:\n\n  * A. Modules are NEVER UNLOADED, only loaded.\n  * B. The EXACT SAME RUSTC VERSION is used to build the app and all modules\n  * C. The EXACT SAME CARGO FEATURES are enabled for crates that both the app\n       and some modules depend on.\n\nUnloading modules (\"A\") would break a significant assumption in all Rust programs: that `'static`\nlasts for the entirety of the program's execution. When unloading a module, we can make something\n`'static` disappear.\n\nAlthough nobody can stop you from unloading modules, what you're writing at this point is no longer\nsafe Rust.\n\nMixing rustc versions (\"B\") might result in differences in struct layouts, for example. For a struct like:\n\n```rust\nstruct Blah {\n    a: u64,\n    b: u32,\n}\n```\n\n...there's no guarantee which field will be first, if there will be padding, what order the fields will\nbe in. We pray that struct layouts match across the same compiler version, but even that might not be\nguaranteed? (citation needed)\n\nMixing cargo feature sets (\"C\") might, again, result in differences in struct layouts:\n\n```rust\nstruct Blah {\n    #[cfg(feature = \"foo\")]\n    a: u64,\n    b: u32\n}\n\n// if the app has `foo` enabled, and we pass a \u0026Blah` to\n// a module that doesn't have `foo` enabled, then the\n// layout won't match.\n```\n\nOr function signatures. Or the (duplicate) code being run at any time.\n\n### Duplicates are unavoidable in `xgraph`\n\nIn the `1graph` model, rustc is able to see the entire dependency graph — as a\nresult, it's able to avoid duplicates of a dependency altogether: if the app\nand some of its modules depend on `tokio`, then there'll be a single\n`libtokio.dylib` that they all depend on — no duplication whatsoever.\n\nIn the `xgraph` model, we're unable to achieve that. By design, the app and all\nof its modules are built and linked in complete isolation. As long as they agree\non a thin FFI (Foreign Function Interface) boundary, which might be provided by\na \"common\" crate everyone depends on, they can be built.\n\nIt is possible for the app and its modules to link dynamically against `tokio`:\nthere will be, for each target (the app is a target, each module is a target),\na `libtokio.dylib` file.\n\nHowever, that file will not have the same contents for each target, because `tokio`\nexposes generic functions.\n\nThis code:\n\n```rust\ntokio::spawn(async move {\n    println!(\"Hello, world!\");\n});\n```\n\nWill cause the `spawn` function to be monomorphized, turning from this:\n\n```rust\npub fn spawn\u003cF\u003e(future: F) -\u003e JoinHandle\u003cF::Output\u003e ⓘ\nwhere\n    F: Future + Send + 'static,\n    F::Output: Send + 'static,\n```\n\nInto something like this (the mangling here is not realistic):\n\n```rust\npub fn spawn__OpaqueType__FOO(future: OpaqueType__FOO) -\u003e JoinHandle\u003c()\u003e ⓘ\n```\n\nIf in another module, we have that code:\n\n```rust\nlet jh = tokio::spawn(async move {\n    // make yourself wanted\n    tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n    println!(\"Oh hey, you're early!\");\n    42\n});\nlet answer = jh.await.unwrap();\n```\n\nThen it will cause _another_ monomorphization of `tokio`'s `spawn` function,\nwhich might look something like this:\n\n```rust\npub fn spawn__OpaqueType__BAR(future: OpaqueType__BAR) -\u003e JoinHandle\u003ci32\u003e ⓘ\n```\n\nAnd now, you'll have:\n\n```\nbin/\n  app/\n    executable\n    libtokio.dylib\n      (exports spawn__OpaqueType__FOO)\n  mod_a/\n    libmod_a.dylib\n    libtokio.dylib\n      (export spawn__OpaqueType__BAR)\n```\n\nAt this point, `executable` refers to its own `libtokio.dylib` (by absolute path),\nand `libmod_a.dylib`, to its own, separate, `libtokio.dylib`.\n\nEven if you were to edit the `DT_NEEDED` / `LC_LOAD_DYLIB` information to have the\nmodules point to `executable`'s version of the dynamic libraries, you would find\nyourself with a \"missing symbol\" error at runtime!\n\n| libtokio.dylib from | Has __FOO | Has __BAR |\n|---------------------|-----------|-----------|\n|     executable      |     ✅    |     ❌    |\n|        mod_a        |     ❌    |     ✅    |\n\nNone of the `libtokio.dylib` files you have contain all the symbols required.\n\nTo make a `libtokio.dylib` file that contains ALL THE SYMBOLS required, you\nwould need rustc to be aware of the whole dependency graph: hence, you'd be back\nto the `1graph` model.\n\nHence, when using the `xgraph`, we accept the reality that code from dependencies\n_will_ be duplicated.\n\n| target | non-generic code | app generics | mod_a generics | mod_b generics |\n|--------|------------------|--------------|----------------|----------------|\n| app    |        ✅        |      ✅      |       ❌       |       ❌       |\n| mod_a  |        ✅        |      ❌      |       ✅       |       ❌       |\n| mod_b  |        ✅        |      ❌      |       ❌       |       ✅       |\n\nThat first column corresponds to all functions, types, etc. that are not generic,\nor that are instantiated the exact same way in each independent depgraph.\n\nThere will be a copy of each of these in the application executable AND in each\n`libmod_etc.dylib` file. That's unavoidable for now.\n\n### Duplicating globals is never okay\n\nNow that we've made our peace with the fact there _will_ be code duplication, and\nthat, as long as that code EXACTLY MATCHES across different copies, it's okay,\nwe need to address the fact that duplicating globals is _never okay_.\n\nIn particular, by globals, we mean:\n\n  * thread-locals (declared via the [std::thread_local!][] macro)\n  * process-locals (more commonly called \"statics\", declared via the [static keyword][])\n\n```rust\nstatic sample_process_local: AtomicU64 = AtomicU64::new(0);\n\nstd::thread_local! {\n    static sample_thread_local: u64 = 42;\n}\n\nfn blah() {\n    let sample_local = 42;\n}\n```\n\n| kind                 | process-local | thread-local | local  |\n|----------------------|---------------|--------------|--------|\n| unique per scope     |      ❌       |      ❌      |   ✅   |\n| unique per thread    |      ❌       |      ✅      |   ✅   |\n| unique per process   |      ✅       |      ✅      |   ✅   |\n\n[std::thread_local!]: https://doc.rust-lang.org/std/macro.thread_local.html\n[static keyword]: https://doc.rust-lang.org/reference/items/static-items.html\n\nTake `tracing`, for example: it lets you emit \"events\" that a \"subscriber\" can process.\nIt's used for structured logging: the event could be of level INFO and include information\nabout some HTTP request, for example.\n\n`tracing` allows registering a \"global\" dispatcher, through [tracing::dispatcher::set_global_default][].\nThis sets a process-global:\n\n[tracing::dispatcher::set_global_default]: https://docs.rs/tracing/latest/tracing/dispatcher/fn.set_global_default.html\n\n```rust\nstatic mut GLOBAL_DISPATCH: Dispatch = Dispatch {\n    subscriber: Kind::Global(\u0026NO_SUBSCRIBER),\n};\n```\n\nThe problem is that, since all targets (the app, all its modules) have their own\ncopy of `tracing`, they also have their own `GLOBAL_DISPATCH` process-local.\n\nIt doesn't matter to `mod_a` if we've registered a global dispatcher from the app:\naccording to `mod_a`'s copy of `GLOBAL_DISPATH` — there's no subscriber!\n\nThere's only one fix for this: everyone must share the same `GLOBAL_DISPATCH`:\nit must be exported from `app`, and imported from all its modules.\n\n## How Rust exports and imports dynamic symbols\n\nIn a perfect world, there'd be a rustc flag like `-C globals-linkage=[import,export]`:\nwe'd set it to `export` for our app, so that it would declare those as exported symbols,\nthe kind you can look up with [dlsym][], and that dynamic libraries you load later can\nuse, because they're part of the set of symbols the dynamic linker-loader searches.\n\n[dlsym]: https://man7.org/linux/man-pages/man3/dlsym.3.html\n\nThere are, however, two roadblocks we must hop.\n\nThe first is that dynamic symbols are not exported for executables. Luckily, there's\na linker flag for that: `-rdynamic` (also known as `--export-dynamic`).\n\nThe second is that _there is no such rustc flag at all_.\n\nExport a static is easy enough. Instead of:\n\n```rust\nstatic MERCHANDISE: u64 = 42;\n```\n\nWe can do:\n\n```rust\n#[used]\nstatic MERCHANDISE: u64 = 42;\n```\n\nAnd we'll get a mangled symbol:\n\n```shell\n❯ cargo build --quiet\n❯ nm -gp ./target/debug/librubicon.dylib | grep MERCHANDISE\n00000000000099f0 S __ZN7rubicon11MERCHANDISE17h03e39e78778de1fdE\n```\n\nThe `#[no_mangle]` attribute implies `#[used]`, and also\ndisables name mangling:\n\n```rust\n#[no_mangle]\nstatic MERCHANDISE: u64 = 42;\n```\n\n```shell\n❯ cargo build --quiet\n❯ nm -gp ./target/debug/librubicon.dylib | grep MERCHANDISE\n00000000000099f0 S _MERCHANDISE\n```\n\n(Just ignore the `_` prefix — linkers are cute like that.)\n\nIn fact, we can even specify our own export name if we want:\n\n```rust\n#[export_name = \"STILL_MERCHANDISE\"]\nstatic PINK_UNICORN: u64 = 42;\n```\n\n```shell\n❯ cargo build --quiet\n❯ nm -gp ./target/debug/librubicon.dylib | grep MERCHANDISE\n00000000000099f0 S _STILL_MERCHANDISE\n```\n\nHowever, when importing, there is no way to opt into mangling.\n\nWe can either import it as-is, without mangling:\n\n```rust\nextern \"C\" {\n    static MERCHANDISE: u64;\n}\n\n// (only here to force the linker to import MERCHANDISE)\n#[used]\nstatic MERCHANDISE_ADDR: \u0026u64 = unsafe { \u0026MERCHANDISE };\n```\n\n```shell\n# needed to avoid link errors: `MERCHANDISE` is not present at link time, it's\n# only expected to be present at load time.\n❯ export RUSTFLAGS=\"-Clink-arg=-undefined -Clink-arg=dynamic_lookup\"\n\n❯ cargo build --quiet\n❯ nm -gp ./target/debug/librubicon.dylib | grep MERCHANDISE\n00000000000e0210 S __ZN7rubicon16MERCHANDISE_ADDR17h2755f244419dcf79E\n                 U _MERCHANDISE\n```\n\nOr we can specify a `link_name` explicitly:\n\n```rust\nextern \"C\" {\n    #[link_name = \"STILL_MERCHANDISE\"]\n    static MERCHANDISE: u64;\n}\n\n// (only here to force the linker to import MERCHANDISE)\n#[used]\nstatic MERCHANDISE_ADDR: \u0026u64 = unsafe { \u0026MERCHANDISE };\n```\n\n```shell\n00000000000e0210 S __ZN7rubicon16MERCHANDISE_ADDR17h2755f244419dcf79E\n                 U _STILL_MERCHANDISE\n```\n\nAll these alternatives, quite frankly, suck.\n\nIf we opt into mangling, we're safe from name collisions, but we _cannot_ import\nthat symbol again (I'm not counting \"manually copying and pasting the mangled name\ninto Rust source code\").\n\nIf we opt out of mangling, two crates that export `CURRENT_STATE` will clash.\n\nIn practice, we have no choice but to opt out of mangling, and make sure there's no\ncollision between the unmangled globals of various crates in the dependency graph —\nwhich means, that's right, we're back to manually prefixing things, like in C.\n\nWe've just covered process-locals. The situation for thread-locals is much the\nsame, except we have to do some more trickery because the internals of `LocalKey`\nare, well, internal, and cannot be accessed from stable Rust.\n\nGetting all these just right is tricky — that's why `rubicon` ships macros, which\nare meant to be used by any crate that has global state, such as `tokio`, `tracing`,\n`parking_lot`, etc.\n\nThis is not as good as a rustc flag, but it's all we got right now. In time, the\nhope is that `rubicon` will disappear.\n\n## Making a crate rubicon-compatible\n\nIf you maintain a crate that has global state, you might want to make it\nrubicon-compatible.\n\n### Depend on rubicon\n\nYou'll need to add a non-optional dependency to it:\n\n```shell\ncargo add rubicon\n```\n\nWithout any features added, it has zero dependencies.\n\nWhen `rubicon/import-globals` or `rubicon/export-globals` is enabled, it will\npull in [paste](https://crates.io/crates/paste), which is a proc-macro: I'm not\nfond of the idea, but I've explored alternatives and token pasting is the best\nI can do right now.\n\nEnabling _both_ features at the same time will yield a compile error, and\nenabling _neither_ will act as if your crate wasn't using rubicon's macros at\nall (so most users of your crate should be completely unaffected).\n\nUsers are in charge of adding their _own_ dependency to `rubicon` and enabling\neither feature — this avoids feature proliferation. Provided that there's only one\ncopy of `rubicon` in the entire depgraph (e.g. everyone is on 3.x), then the scheme\nworks.\n\n### Macro your thread-locals\n\n`rubicon::thread_local!` is a drop-in replacement for `std::thread_local!`.\n\nBefore:\n\n```rust\nstd::thread_local! {\n    static BUF: RefCell\u003cString\u003e = RefCell::new(String::new());\n}\n```\n\nAfter:\n\n```rust\nrubicon::thread_local! {\n    static BUF: RefCell\u003cString\u003e = RefCell::new(String::new());\n}\n```\n\nHowever, keep in mind that, whenever import/export is enabled, mangling will\nbe disabled for your static. Thus, it might be a good idea to preemptively\nprefix it:\n\n```rust\nrubicon::thread_local! {\n    static MY_CRATE_BUF: RefCell\u003cString\u003e = RefCell::new(String::new());\n}\n```\n\n### Macro your statics\n\nBefore:\n\n```rust\nstatic DISPATCHERS: Dispatchers = Dispatchers::new();\nstatic CALLSITES: Callsites = Callsites {\n    list_head: AtomicPtr::new(ptr::null_mut()),\n    has_locked_callsites: AtomicBool::new(false),\n};\nstatic DISPATCHERS: Dispatchers = Dispatchers::new();\nstatic LOCKED_CALLSITES: Lazy\u003cMutex\u003cVec\u003c\u0026'static dyn Callsite\u003e\u003e\u003e = Lazy::new(Default::default);\n```\n\nAfter:\n\n```rust\nrubicon::process_local! {\n    static DISPATCHERS: Dispatchers = Dispatchers::new();\n    static CALLSITES: Callsites = Callsites {\n        list_head: AtomicPtr::new(ptr::null_mut()),\n        has_locked_callsites: AtomicBool::new(false),\n    };\n    static DISPATCHERS: Dispatchers = Dispatchers::new();\n    static LOCKED_CALLSITES: Lazy\u003cMutex\u003cVec\u003c\u0026'static dyn Callsite\u003e\u003e\u003e = Lazy::new(Default::default);\n}\n```\n\nBoth `thread_local!` and `process_local!` support multiple definitions.\n\nIn addition, `process_local!` supports `static mut`, should you _really_ need it (looking\nat you tracing-core).\n\n### Mind your dependencies\n\nSometimes thread-locals and statics hide in the darndest of places.\n\nFor example, `tokio` depends on `parking_lot` which has global state (did you know?)\n\n```rust\n/// Holds the pointer to the currently active `HashTable`.\n///\n/// # Safety\n///\n/// Except for the initial value of null, it must always point to a valid `HashTable` instance.\n/// Any `HashTable` this global static has ever pointed to must never be freed.\nstatic PARKING_LOT_HASHTABLE: AtomicPtr\u003cHashTable\u003e = AtomicPtr::new(ptr::null_mut());\n```\n\n## Implementing the `xgraph` model\n\nAssuming all your dependencies are rubicon-compatible, you can implement the `xgraph` model!\n\nIn terms of crates, you'll need\n\n  * `bin`, a bin crate, depends on `exports`, and `libloading`\n  * `exports`, a lib crate, `crate-type=[\"dylib\"]` (that's just \"dye lib\")\n    * depends on _all_ your rubicon-compatible dependencies\n    * depends on `rubicon` with feature `export-globals` enabled\n  * `mod_a`, a lib crate, `crate-type=[\"cdylib\"]` (that's \"see dye lib\")\n    * depends on `rubicon` with feature `import-globals` enabled\n  * `mod_b`, like `mod_a`\n  * `mod_c`, like `mod_a`\n  * etc.\n\n\u003e The `exports` crate is needed to bring all globals in the address space in a way\n\u003e that the dynamic linker can understand.\n\u003e\n\u003e _Technically_ `-rdynamic` should help there, but I couldn't get it to work.\n\nThat's about it. Don't forget the invariants!\n\n  * A. Modules are NEVER UNLOADED, only loaded.\n  * B. The EXACT SAME RUSTC VERSION is used to build the app and all modules\n  * C. The EXACT SAME CARGO FEATURES are enabled for crates that both the app\n      and some modules depend on.\n\nYou can find a full example in `test-crates/` in [the rubicon repository](https://github.com/bearcove/rubicon).\n\n## License\n\nThis project is primarily distributed under the terms of both the MIT license\nand the Apache License (Version 2.0).\n\nSee [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbearcove%2Frubicon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbearcove%2Frubicon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbearcove%2Frubicon/lists"}