How to implement Dependency Injection in Mojo?

The following doesnt work (yet) in Mojo it seems
trait Printer:
fn print_it(self,text:String):
...
@value
struct BoringPrinter(Printer):
fn print_it(self,text:String):
print(text)

fn lets_print(p:Printer,text:String):
p.print_it(text)

fn main():
var bp = BoringPrinter()
lets_print(bp,"let's sing a song")
trait Printer:
fn print_it(self,text:String):
...
@value
struct BoringPrinter(Printer):
fn print_it(self,text:String):
print(text)

fn lets_print(p:Printer,text:String):
p.print_it(text)

fn main():
var bp = BoringPrinter()
lets_print(bp,"let's sing a song")
i get the following error:
error: invalid call to 'lets_print': argument #0 cannot be converted from 'BoringPrinter' to 'Printer'
error: invalid call to 'lets_print': argument #0 cannot be converted from 'BoringPrinter' to 'Printer'
Is there a way to implement in Mojo what i am trying to do here. As we don't have inheritence yet i want to use Dependency Injection but without this i feel loooooost. Any advice on this highly appreciated. Not knowing how to implement this discourages me to think of more interesting framework like Mojo projects unfortunately.
5 Replies
Martin Dudek
Martin Dudek4mo ago
i notice that the following works:
trait Printer:
fn print_it(self,text:String):
...
@value
struct BoringPrinter(Printer):
fn print_it(self,text:String):
print(text)

fn lets_print[PR:Printer](p:PR,text:String):
p.print_it(text)

fn main():
var bp = BoringPrinter()
lets_print[PR=BoringPrinter](bp,"let's sing a song")
trait Printer:
fn print_it(self,text:String):
...
@value
struct BoringPrinter(Printer):
fn print_it(self,text:String):
print(text)

fn lets_print[PR:Printer](p:PR,text:String):
p.print_it(text)

fn main():
var bp = BoringPrinter()
lets_print[PR=BoringPrinter](bp,"let's sing a song")
but having to specify the struct which confirms with the trait as parameter won't get me far in more realistic examples i am afraid.
Michael K
Michael K4mo ago
I think you are working through an example like "Quackable" here - https://docs.modular.com/mojo/manual/traits#using-traits. A trait is not a concrete type so I don't think it can be used as an argument type. The type can be inferred at the call site though so you don't need to use the square brackets when calling lets_print. Lastly there is a note in the link saying they hope to make the syntax less verbose in the future.
Traits | Modular Docs
Define shared behavior for types.
Martin Dudek
Martin Dudek4mo ago
oh okay, million thanks, i actually dont have to specify the concrete struct as parameter, the function just has to defined like that, excellent. I completely missed that for the record, as @Michael Kowalski pointed out, the following works
trait Printer:
fn print_it(self,text:String):
...
@value
struct BoringPrinter(Printer):
fn print_it(self,text:String):
print(text)

@value
struct FancyPrinter(Printer):
fn print_it(self,text:String):
print("--------------------")
print(text)
print("--------------------")

fn lets_print[PR:Printer](p:PR,text:String):
p.print_it(text)

fn main():
var bp = BoringPrinter()
var fp = FancyPrinter()
lets_print(bp,"let's sing a song")
lets_print(fp,"let's dance")
trait Printer:
fn print_it(self,text:String):
...
@value
struct BoringPrinter(Printer):
fn print_it(self,text:String):
print(text)

@value
struct FancyPrinter(Printer):
fn print_it(self,text:String):
print("--------------------")
print(text)
print("--------------------")

fn lets_print[PR:Printer](p:PR,text:String):
p.print_it(text)

fn main():
var bp = BoringPrinter()
var fp = FancyPrinter()
lets_print(bp,"let's sing a song")
lets_print(fp,"let's dance")
ok i see now that this does not work
var p:Printer
p = BoringPrinter()
var p:Printer
p = BoringPrinter()
as @Michael Kowalski said, " A trait is not a concrete type so I don't think it can be used as an argument type. " will see how far i come with the new insights here, thanks again Still not really happy with my understanding, hope someone can help me further with this. Right now I only manage to implement Dependency Injection via struct parameter at compile time. Is there a way to inject dynamically via __init__ Any advice on this highly appreciated. Thx here is what i do so far :
trait Printer:
fn __init__(inout self):
...
fn print_it(self,text:String):
...
@value
struct BoringPrinter(Printer):
fn print_it(self,text:String):
print(text)

@value
struct FancyPrinter(Printer):
fn print_it(self,text:String):
print("\n-------------------")
print(text)
print("--------------------\n")

struct PrintManager[PR:Printer]:
var printer:PR

fn __init__(inout self):
self.printer = PR()

fn lets_print(self,text:String):
self.printer.print_it(text)

fn do_other_things(self):
pass

fn main():
var bpm = PrintManager[BoringPrinter]()
var fpm = PrintManager[FancyPrinter]()

bpm.lets_print("let's sing a song")
fpm.lets_print("let's dance")
trait Printer:
fn __init__(inout self):
...
fn print_it(self,text:String):
...
@value
struct BoringPrinter(Printer):
fn print_it(self,text:String):
print(text)

@value
struct FancyPrinter(Printer):
fn print_it(self,text:String):
print("\n-------------------")
print(text)
print("--------------------\n")

struct PrintManager[PR:Printer]:
var printer:PR

fn __init__(inout self):
self.printer = PR()

fn lets_print(self,text:String):
self.printer.print_it(text)

fn do_other_things(self):
pass

fn main():
var bpm = PrintManager[BoringPrinter]()
var fpm = PrintManager[FancyPrinter]()

bpm.lets_print("let's sing a song")
fpm.lets_print("let's dance")
Ryulord
Ryulord4mo ago
This isn't possible and it isn't ever going to be possible with structs. You'll have to wait for classes. The reason for this is that the compiler needs to know the concrete type you're using at compile time because this allows for certain optimizations to happen that aren't present in other OO languages. Specifically, your PrintManager isn't going to just contain a pointer to a Printer (unless you say it should), it will simply contain all of Printer's fields within itself in a single contiguous chunk of memory. To do this, it needs to know how big Printer is, which could vary between concrete implementations. Additionally, Mojo doesn't use vtables to achieve polymorphism. Determining the specific concrete type at compile time allows the compiler to know exactly what needs to be called when you call a method on that type without any extra work at runtime, which is necessary when using a vtable. This is all part of Mojo's goal of being high performance. Classes will still be able to do all the crazy dynamic stuff though if you don't mind the performance hit.
Martin Dudek
Martin Dudek4mo ago
thanks a lot @Ryulord for these explanations, very helpful for my overall understanding of Mojo and how to design programs 🙏
Want results from more Discord servers?
Add your server