Two versions of app: open source and enterprise

Two versions of app: open source and enterprise

I've got an open source app written in Rust. I'm in the process of adding an enterprise version of it. 99% of the functionality is in the open source version, and the enterprise version just needs to replace a few of the underlying functions. I'd like to keep the enterprise code closed-source.

What's the best project layout?

It would have to be two separate git repos (I think) if I want one to be open and the other closed.

I could have the enterprise version depend on the open source version, but I'm having a hard time replacing the underlying functions in the open source version with enterprise functions. Rust won't let me swap out code that exists in a dependency, even with a feature flag, because we'd have a circular dependency: enterprise depends on the open source, but the open source must access enterprise code.

I've tried tricks with the #[path] directive to have the open source pull a file from the enterprise project, but that has led to dependency hell.

Surely this is a solved problem. How do other people do it?

Answer

Traits can do this.

Say you have a function in your open source version that does something:

pub fn run() {
    println!("this is open source");
}

You can make a trait that has items for all the things you want to change. Then make your run function generic.

pub trait Version {
    fn f();
}

pub fn run<V: Version>() {
    V::f();
}

Then you can implement the trait with your open source behavior and use it in your app.

pub struct OpenSource;

impl Version for OpenSource {
    fn f() {
        println!("this is open source");
    }
}

fn main() {
    run::<OpenSource>();
}

Then the enterprise version can include the open source library as a dependency, implement the trait, and use that implementation in its app.

pub struct Enterprise;

impl your_app::Version for Enterprise {
    fn f() {
        println!("this is enterprise");
    }
}

fn main() {
    your_app::run::<Enterprise>();
}

You will most likely have many functions and possibly some associated types in your trait, and need to pass the generic through many functions.

Enjoyed this question?

Check out more content on our blog or follow us on social media.

Browse more questions