--export-dynamic does sort of work. I end up with around 12,000 symbols in my program, but focusing just on the Lua component which I have compiled to a static library and then linked into my executable the symbols appear with the correct visibility that I defined in the original static library, so this is good. There is the small issue that any libraries that I use that don't correctly define visibility will be exported, but this isn't Rust's problem, that rests with those libraries so OK.
The bigger issue that leads to the 12,000 symbols is that every rust symbol which is public anywhere (including dependencies) seems to be being exported as dynamic but with type a and address 00000000. I cannot find very much information about these symbols but that they are local absolute symbols according to the OpenGroup specification (they don't actually appear in the Gnu nm manpage). This may be a bug since I cannot understand how a local symbol being dynamic is in any way helpful. These symbols appear in both release and debug targets.
I suppose then that if these type a symbols can be dealt with and the --export-dynamic flag somehow integrated into Cargo then this would be a good solution to the problem I am having.
Actually, given that Rust does a reasonable job of hiding its symbols properly, then --export-dynamic could actually be set as the default option and the symbol exports I need would be automatic based on the options I set in the original C library. That would be nice.