Moving a C/C++ project to Rust

Hi,

I'm working on a debugger called ProDBG (over here https://github.com/emoon/ProDBG) it's mostly written in C++ (with a bunch of C also)

I have considered moving a majority of my own code over to Rust instead but I'm a bit unsure of the best approach for this.

Currently I'm using the Tundra build system to build all the code (Can be found here https://github.com/deplinenoise/tundra)

I think my approach would be to move things in steps over to Rust instead of just trying to rewrite everything. For example the Plugins for the debugger uses a C API (not C++) in order to make it easy to interface with other langs so I can perhaps move some of that over first.

Now would it be best to.

  1. Move away from Tundra to use Cargo for everything. What worries me a bit here is incremental builds which are very fast in Tundra (uses Lua for config and C++ for building)

  2. Add Cargo support to Tundra.

  3. Skip Cargo and focus on just using rustc and have it as a "regular" compiler for the rest of the C/C++ code.

If anyone else has done something similar I would love to hear about which approach you have taken and the pros and cons with it.

Cheers!

4 Likes

If you move to cargo, you can split up your project into many small crates to retain incremental building. You can slowly move to cargo by using build scripts to call into you regular build system.

That sounds like an interesting approach. Thanks!

One issue with #3 I ran into recently: Cargo projects tend to play nicer with tools like multirust, because dependencies get compiled into a per-Rust directory. I recently used a Rust project which used plain old make, and thought I found a Rust bug when I switched between nightly and stable, causing a crash because the project put dependencies in its own deps/ directory...

Thanks. Good to know for sure. I think the approach of having Cargo calling my other build system and then work from there is the approach I will try to take as suggested by @oli_obk

Please note, build scripts are run before main compilation run, and there's no way as of now to run a script after the compilation from cargo.

Thanks for the info. I think even with that limitation I should be able to handle it.

Cargo looks alike package system, not a comprehensive build system. Don't forgive you can use rustc directly from other systems like Tundra. In our projects we use Cargo for builds Rust only, Erlang parts we build with Rebar, C parts and whole project with SCons. Sounds excessive, but not a problem.

Some time later we'll rewrite many parts with Rust, because we plan it. As example, we've test Rust a reimplementation of our Erlang's service now. It works 9x faster, and we'll drop Erlang's part, I think. But the main reason is not a speed. Rust has more active community with fast developing external libs. We can solve more comlicated tasks with Rust. That's a reason. But some things changes too fast and sometimes it would be better to rewrite project fully from scratch, because you can take advantages of many cool language features and follow the rapid improvements.

4 Likes

Thanks for a great reply.

Do you then have some "master" script that builds all the various parts (calls Cargo, Rebar and SCons) and how do you deal with dependencies between them?

You have a great point about features and such. I think I will start with rewriting some plugin in Rust. The good thing is that the plugins are quite standalone in my project while it still needs to talk with C code (I also still want to keep that to allow other langs for plugins in the future)

I don't have shared example of a build scripts, but we use similar like:

# SConstruct example

import os
cargo = Builder(action="cargo build", chdir=1)
# and another builders

env = Environment(
    ENV=os.environ,
    BUILDERS=dict(
        Luac=luac,
        Jade=jade,
        Less=less,
        Cargo=cargo,
        Rebar=rebar,
    )
)

RUST_SOURCES = ['src/main.rs']
env_cargo = env.Clone()
env_cargo.Cargo('target/debug/program', RUST_SOURCES)

SCons is never forget how-to build system ) It's automatically check file's versions and never build twice.
Some builders we've implemented internally as SCons extensions.

To resolve external dependencies we use Bower, Rebar, etc. with bootstrap shell script. It also possible to implement as build routine, but we decide never mix it. Cargo do it (deps and build) automatically. It's a nice bonus which works fine.

In another words: dependency resolving is a task for package managers, testing for test tools, assembing for build systems, deployment for devops toolchain, benchmarking for another tools. Cargo has an one button for the multiple of this tasks, but It's Rust oriented: test, benches, etc.

I think for native or system projects like your Cargo will be a deliverance :bath:

3 Likes

Alright. That makes sense. Thanks!

Brining this back to life as I have started to look at this.

I have it up and running now using Cargo that builds my main project and some plugins and that all works fine. The main project also has a build.rs which kicks off tundra to build the native code. Now some issues.

  • It seems that Cargo 'eats' all the std output from Tundra. Is there a way to disable that?
  • If I change some native code and do cargo build I want it to build all the native code again. Is there some way to tell Cargo to always execute build.rs? (Or can I fool it is some way)

Cheers!

Looking at the project, it isn't Cargo that's eating the output, it's this:

let _ = Command::new(tundra)
                .arg("-v")
                .arg(command)
                .output()
                .unwrap_or_else(|e| { 
        panic!("Unable to execute tundra: {}", e)

Switching output() for status() should stop the STDOUT from being eaten.

Thanks but it doesn't seem to help (also doing a println!(...) doesn't show either)

Right now I worked around the problem by having the bash script calling the tundra build step first and then Cargo to build the Rust code. I think that should work fine as most people will use that script when building anyway.

I think this is your relevant Cargo issue. https://github.com/rust-lang/cargo/issues/1106

1 Like

Ah yes it is. Thanks for the link!

Another question.

Is it possible in Cargo to refer to other Cargo projects so they build?

Something like

[project]
path_to_project = "foo/bar"

In this case the projects are dynamic libs so my main project shouldn't "depend" on them in a direct sense if you understand what I mean.

If I do something like

[dependencies]
plugin = { path = "some/plugin" }

That makes it build and all but isn't really what I want.

One way would of course to define all projects in the "root" Cargo.toml file and may that is what I should do?

Are you looking for path dependencies? If in Cargo toml you have

[dependencies]
foobar = { path: "foo/bar"}

Then foobar won't be downloaded from crates.io. Instead, cargo will look for a Cargo.toml in foo/bar and build it.

docs: Page Moved

Yeah that is what I currently use but it really doesn't work the way I want it.

It depends on a dynamic lib which actually isn't built correctly and it generates a file in deps/ but the actual dynamic lib isn't built (or at least now written to the output directory) but if I got to "foo/bar" and do cargo build there it builds it correct (and generates the dynamic lib in the correct directory)