class Processor {
public void process(String data) {
}
}
class Storage {
public void save(String data) {
}
}
abstract class Base {
public boolean start() {
mExecutor.execute(this::onStart);
return true;
}
public boolean stop() {
mExecutor.execute(this::onStop);
return true;
}
abstract void onStart();
abstract void onStop();
protected Executor mExecutor = Executors.newSingleThreadExecutor();
protected Processor mProcessor = new Processor();
protected Storage mStorage = new Storage();
}
class DeriveA extends Base {
@Override
void onStart() {
System.out.println("DeriveA onStart");
}
public void methodA(String data) {
mExecutor.execute(() -> {
// do some logic a
mProcessor.process(data);
mStorage.save(data);
});
}
public void methodB(String data) {
mExecutor.execute(() -> {
// do some logic b
mProcessor.process(data);
mStorage.save(data);
});
}
@Override
void onStop() {
System.out.println("DeriveA onStop");
}
}
class DeriveB extends Base {
@Override
void onStart() {
System.out.println("DeriveB onStart");
}
public void methodC(String data) {
mExecutor.execute(() -> {
// do some logic c
mProcessor.process(data);
mStorage.save(data);
});
}
public void methodD(String data) {
mExecutor.execute(() -> {
// do some logic d
mProcessor.process(data);
mStorage.save(data);
});
}
@Override
void onStop() {
System.out.println("DeriveB onStop");
}
}
class Test {
public static void main(String[] args) {
DeriveA a = new DeriveA();
a.start();
a.methodA("hello");
a.methodB("world");
a.stop();
DeriveB b = new DeriveB();
b.start();
b.methodC("hello");
b.methodD("world");
b.stop();
}
}
I want to translate the above Java code into Rust, but Rust doesn’t support inheritance. What idiomatic ways does Rust provide to achieve the effect of this Java code?
It looks like you might find rayon interesting, which is a thread pool manager. If you really want just a single thread consuming from a queue that's just a thread and mpsc channel.
You can leave the factories behind, they are not needed and are not idiomatic Rust.
Thank you for your response, but the key point I want to ask about is not thread scheduling. Instead, it’s whether Rust has the capability to achieve the abstraction effect in the Java code mentioned above. Specifically, adding a new DeriveC should only require inheriting from Base, allowing it to reuse the code in Base. Using default implementations in traits can achieve a similar effect, but since traits cannot contain fields, it cannot directly achieve the same effect as described.
I suggest searching the forum for "inheritance". Similar questions are discussed at least every few months. But the quick answer is no -- as you already pointed out, fields cannot be inherited. Also see: https://doc.rust-lang.org/book/ch17-00-oop.html
In general, composition is used rather than inheritance, and many typical OO patterns are not used.
Traits are good for polymorphism, but limited when it comes to reusing implementations.
I would
create a trait "Runnable" for the start, on_start, stop and on_stop and a "ExcutionUnit" struct, which handles the implementation details (SingleThreadExecutor, Processor, Storage)
Something like in Compiler Explorer
have a short meditation about how designs sometimes don't translate nicely between languages and then
The example provided is demonstrating code reuse, not abstraction. Allow me to illustrate the difference:
The String class is a good example of an abstraction. It abstracts away all of the gritty details around strings, hiding the fact that computers do not have any native operations over strings or characters (just numbers).
Perhaps the most common examples of code reuse in Rust are macros. Rather than copy-pasting the same boilerplate for stdio and string formatting, we use println!().
Traits can contain getter and setter methods. Which are like the interface for data members, oddly enough! I don't recommend doing this, but it's an option.
Composition is helpful! It may not suitably reduce boilerplate like the thread start/stop thing shown in OP. Those details are specific to the thread processor/base class thing itself.
But it's a bad abstraction! A good abstraction would completely hide the details of starting and stopping: Automatically start when created, automatically stop when destroyed.
It's not exactly idiomatic Rust and has to be a little more explicit than the Java code in places, but a reasonably direct translation of this sample is possible. This is just a quick-and-dirty example, so it doesn't support everything that Java's inheritance system does— only the parts you've explicitly used.
Mostly, I had to make the extension fields explicit in Base via a generic type parameter, which is what a Java/C++ compiler will be doing in the background anyway. The larger changes came from how I hacked together a SingleThreadedExecutor mockup that would wait for the worker thread to finish before allowing the program to exit.