 I'm going to get started, welcome everyone to RubyConf, hope you guys are having a good time so far, I am. I'm going to be talking about Ruby and Go and how you can use both of the languages together inside the Ruby VM to write fast Ruby code. But first I'm going to tell you about who I am once I get my thing working here, all right cool. So who am I, my name is Lauren Siegel, I have a Twitter account and a blog that I occasionally update you can follow me, check out what I write about random things. I've been working on a bunch of different things over the last couple of years, tools for Ruby, tools for the cloud, I was working on games for a little bit, and most recently I am working on music tooling at Splice. So that means I've been working with a lot of different languages, a lot of different technologies, Ruby, JavaScript, C sharp Go, Objective C, TypeScript, all this fun stuff, so lots of different languages. And so I figured I would take that opportunity to sort of share with you some of my experience using two of my favorite languages, which is Ruby and Go. So let's start with Ruby. Ruby is I guess for the purposes of this talk, slow and inefficient. But it's a beautiful language, I like it a lot, it is my favorite language. But it is definitely slower than Go. And I could definitely talk more about Ruby and tell you all the awesome stuff and all the bad stuff, but we're here at RubyConf. Hopefully a lot of you guys are already Ruby developers, if you're not, there's plenty more at RubyConf to learn about Ruby and stuff like that. So I'm gonna sort of skip this little Ruby tidbit part and just talk about the language you may not know much about, which is Go. So Go is fast and simple. We kind of know it as this simple language that is also as fast as C. But that's about it. Well, okay, not really. There's more to Go than just the fast and simple part of Go. Go actually has a bunch of different features. It has structural typing, it has a really fast garbage collector. It has interoperability with C, so you can write systems level type programming with Go. It has a really good concurrency story. It has really fast performance and a very simple grammar, as I just mentioned. But the key thing, if you guys haven't seen Go and are sort of just learning it now, the key thing to really understand about Go, I think, in my opinion, is the structural typing portion of Go. And I think that's really what fundamentally makes Go the language itself. And so this is a bit of a simplification. But you could basically think of Go as language with two features. The sort of the structs and types that declare data and the functions that define behavior for that data. And if you look at Go this way, it makes it a lot easier to sort of understand how code is set up. If you first start reading it. And you might ask sort of like doesn't every language have code and data separate and that's true but in Go the separation is much more explicit. So for example, in an object-oriented language like Ruby, when we define a class, we define a class as a module with methods inside of it. And also occasionally we'll put attribute accessors inside of it. And all that stuff is sort of in one big bundle. And in fact, when we define attribute accessors, they're really just method calls. So there really isn't even a differentiation between where the data lives and how the data behaves. So that's sort of how Ruby works. And in Go what you actually have is these struct types. So on the left you see the person struct up top. And you see that it's defining just the person structure as data. And it's not really caring about how it behaves, it's just defining the attributes that define a person and that's all. And so that's how structures are defined. And on the right hand side you could kind of see how functions operate on the data. So we might have functions that are just class level type functions that just perform data on no specific struct. Or you might have a function at the bottom half over there where you would have something more like an instance method in Ruby where you're operating on a type. But these are defined separately and if you think about these separately. Oops, we have a problem. Sorry about that. I gotta make my computer not go to sleep. Okay, so you can basically have functions and you can have structs. And sort of understanding these two things sort of gets you 80% way they're in Go. And the reason it gets you 80% the way they're in Go is because there are no other major abstractions in the language. This is basically it. I mean, there's a couple of other sort of syntactic sugars for asynchronous behavior. But this is pretty much the entirety of the syntax of the language. So if you understand this part of Go, you pretty much understand most of it. Which is good for us and it's also good for the tool that I wrote which basically looks at the structure definition. So we'll see why that matters in a bit. There's obviously more you can learn about Go. You can go read all these websites. There's a good tour on Go Lang that you guys can read through if you wanna learn more about Go. I'm not gonna dive too deep into the language. But you can go read about it and learn about all the little bits and pieces of Go. It has a good playground on the website. So you can go play with it in the browser. You don't have to install on your machine if you wanna learn Go. It's kinda fun. But the problem here is that, so Go is like, it's a fast language. It's meant for business use cases. It's like a real language. So to me that's kinda boring. I don't really care about that. I mean, I'm in it for this. I'm in it for the toys, right? So what I really like about Ruby is the fact that it's a language that you can manipulate and play with and sort of bring to the edge and sort of do really weird things with. Because the language is so expressive that you can do so many things with Ruby. And that's sort of what drives me as a programmer to a language like Ruby. So I wanna tell you guys a little story about what got me to writing this tool called Gorb. And basically what happened was a couple months ago I was relearning Go. I was about to start using it professionally for a while. And I was relearning Go and I was writing a lot of Ruby at the time. So I sort of writing Go, relearning Go and also trying to write Ruby code. I was writing Go and then also realizing how much of Ruby is missing in Go and how much more I like Ruby than Go. And it was really bothering me. I couldn't stop thinking about writing Ruby code. So I knew that Go is the right tool for the job here. So I thought of a way that I could write Ruby and Go at the same time and also make my Ruby faster by making it work with Go. So I decided to write a tool that would help me write faster Ruby code. And in doing so, I would get to learn more about Go and relearn all the Go that I needed to. And I was going to call this tool Gorb. So Gorb has a GitHub page that you guys can go visit. It is on my GitHub slash lcgle slash Gorb. It has examples and a read me and a bunch of code that you can go look at and play with. It's very new, so it doesn't really have. It's not complete. There's a lot more stuff to do. So if you want to open pull requests, you totally can. But before you go in and open pull requests, maybe I should tell you more about what Gorb is. So two things at the top, what Gorb is not. Gorb does not stand for Gorbachev. And Gorb does not stand for Gorbipuff. Gorb stands for Go Ruby. And it is basically a tool that lets you write Go. Write Go is you would normally write Go. Fast Go that you would use in production. And run that fast Go inside of the Ruby VM. And that's pretty much all it is. So how do we connect a language like Go? How do we actually run Go code inside of Ruby? And the answer to this question is a thing called native extensions. So native extensions are a way for Ruby to sort of load machine code or C code or dynamic libraries into the Ruby VM and allow you to run that machine code as if it were part of the Ruby sort of world. And so that's how sort of my SQL gems or other native integrations work is basically have a bunch of C code that just gets thrown into the Ruby VM. That way you can interact with your operating system or do weird things that are usually not available to the Ruby world. So Gorb takes advantage of native extensions to do this. Native extensions, at least in MRI and MRI is the Ruby that we use and no, not JRuby, not Rubinius, the Ruby I will, whenever I say Ruby now, I'm talking about MRI specifically. Ruby is written in C and Ruby's API is known as C Ruby. It is basically the code that drives the Ruby implementation, the API that drives Ruby itself. This is what some of the Ruby implementation looks like. This is just a snippet from the Ruby source code. This is the C Ruby that you would typically see if you were reading through Ruby's code. C Ruby is C code, which means that writing C Ruby is hard, because writing C is hard. And we don't wanna typically have to write C code when we are writing as programmers. I mean, I don't like C, a lot of people are not necessarily comfortable and familiar with C these days. It's not exactly the easiest language to use. So stuff like this is not exactly easy to read or maintain or write. You have to deal with macros, especially in the C Ruby API. A lot of macros are fun to use, they're complicated. They have lots of problems. You have to deal with your own memory management, which is problematic if you're not experienced in memory management, manual memory management. You have to deal with compiler issues. Ruby's MKMF native extension helper stuff deals with a lot of that for you, but there's still always gonna be some kind of compiler issue that you're gonna run into when you're dealing with cross-platform C extensions. So that stuff isn't fun. And finally, it doesn't help that the C Ruby API itself, the documentation isn't exactly the best documentation out there. Mostly because a lot of this stuff doesn't actually get touched a lot. So you're often finding edge cases in the documentation. It's not fun. So it would be great if we can write fast code that was used by a native extension but didn't have to worry about C. And so writing go is definitely easier than writing C in my experience. But the problem is that if we're writing go, we're still writing C. Because we need to use the C Ruby API to connect our go to as a native extension to Ruby's VM. And that is not easy. So as an example, this is what some of the output that Gorb spits out for you is this is a wrapper method for a method called isPrime which basically just calculates whether a number is prime. It takes one input number and it spits out a true or false value. This is like 10 lines of code to wrap this function. And the implementation is actually not here. The implementation is in another file. The actual implementation for isPrime. So this is only the wrapper code to let you use this inside of Ruby. And this is like 10 lines of code. And imagine doing this for 10 methods, for 20 methods, for complex methods that take blocks, raise exceptions, parse multiple arguments, parse a var argument. This stuff gets complicated really fast. So Gorb simplifies this by basically automating away that problem. It will automatically generate these wrappers for you. And that's what Gorb does. The goal here is to write this on the left hand side, just your regular go code and have Gorb generate for you the implementation details. And basically it lets you use it from Ruby as if you were using regular Ruby and running the go code on the left hand side. Not worrying about anything on that right hand side on that screen. And the great thing is that you're writing go code so the code is fast, how fast you ask. Well, so there's this little benchmark in the examples. This is a very small micro benchmark that is obviously a micro benchmark. And you can get, basically, you can get 10X speeds. You can get faster than 10X speeds by using go. It's not that surprising because we know that go is C level fast. It's almost as fast as C. So it's not surprising that we're getting these kinds of speeds from go. But it is interesting if you think about it. If you do have problems in the real world, you have these bottlenecks, CPU bottlenecks, it is interesting to think that you can actually write go code that is easier to maintain than C. Actually maintainable, easy to write, easy to read. But still get that performance benefit from Ruby. So I am now going to sort of jump into a demo here to show you guys what this code looks like. So if I could get this to work, I'm gonna have to move this onto the screen. Let's see. So I have Gorb running inside of a Docker image here. There's a Docker image in the repo. If you pull down the repo, you get a Docker file that you can build and run. So you don't have to worry about setting up go or setting up Ruby. It's all there for you. And Gorb has a command called, no, why did my keyboard stop working? That's great. No, no, technical difficulties. Wait, I think I know what it is. I didn't. Let's try this again. Sorry about the technical difficulties. Okay, we can fix this. We have the tools. So I'm just going into my go repo directory here. So there's a start Docker thing. Hopefully this works. Hopefully Docker didn't just die, and I have to restart Docker. Okay, I'm gonna restart Docker. Let's see what happens. Sorry about that. Great, let's see if this works. There we go, yes, okay, sweet. All right, you don't have to clap now. I didn't do anything. All right, so as I said, I'm inside of a Docker image here. Let's just get this window not weird. Okay, and so Gorb has a command called Gorbgen, and Gorbgen basically is the command that generates these wrapper files for you. So you basically pass a go package to Gorbgen, and it'll spit out code that can be compiled as a native extension in Ruby, and you could load that from Ruby. Before I generate code, I might as well show you guys what some of the code that we will be generating for is. So here's our Fibonacci example that I had before. So here's a Fibonacci example that we had before. This is basically the code that was powering that benchmark that I showed you, and this is the actual benchmark code that we were running with the Ruby version up top, and as you can see, so what we did is we created a struct that Gorb will then turn into a class for us, and we attach a method onto the struct called fib that basically performs the necessary math functions for us. And then from Ruby, all we're doing is we're loading up the library as if it were regular Ruby code, and then just calling Fibonacci.new because it's imported as a class inside of Ruby. So we now have a class, and then we just call the fib method, which basically calls out to go. So that is actually calling go from Ruby right there. So if I were to call Gorb gen test slash fib, that's where the file, that's where the package is relative to my current directory, I can generate the file. I can also build the file, so it'll automatically build the native extension for us in one step. You don't have to go to Ruby and do make and all that magic. It'll automatically do that for us, so it has now built that SO file. And we can actually use that SO file directly, so if I were to require x slash test slash fib slash fib, that's where that SO file lives on disk. We now have this module, oops, test fib. There's the module, and this is a class that we, fib, there's the class that we generated, and we can create a new one. We can call fib on it, oops. We can call fib on it, there we go. So you can, and this is calling out directly to go code that we compiled. So that's sort of how you would use Gorb. This is sort of what the code is generated looks like, so as I mentioned, it gets kind of unwieldly fast. This is what you would have to write if you were writing this yourself. So as you can see, there's a lot of border plate here for, we have that isPrime method that's just there for fun, so there's that isPrime example up top, but this is the fib and Aichi code, so you have boilerplate for the, creating the structure for the class, you have boilerplate for like allocating the class and creating an object, a go object for you and keeping track of that, and then you actually have the fib method over here down at the bottom that actually calls out to the fib function. So all that stuff is generated for you and all that stuff is compiled for you, you don't have to worry about any of it. This is the boilerplate for actually hooking this up to Ruby and telling Ruby that we have this method called isPrime, we also have a class called fibonacci and we have this method on the fibonacci class called fib. So all this stuff is hooked up for us, we don't have to worry about it, this is what Gorb does for us, and we just have to worry about code. So let's look at more code, for example. This is, we can look at code that does HTTP stuff. So if you wanted to write an HTTP server or I wanted to write an HTTP sort of app that runs on HTTP over the web, you can use Go's HTTP web server if you wanted to and just serve methods, serve bodies from that Go HTTP server. So this is implementation of just a regular sort of like rack style application that is hosted on slash Gorb at some address and basically this Go code will return a body back to us as a user and this is the actual implementation. So we call serve which calls this method up top, we give it an address to bind to, so we are binding to 8080 and then we give it a call back and Gorb is smart enough to turn these call back functions into blocks for us. So we get a nice block down here in Ruby and we can then perform stuff in Ruby. And then we have this little client method. I think we have a client. So I wrote this little client application that basically starts a server and sends a request to that server for us. So we'll run this little program here and see what happens. So I don't need to generate this stuff, I generated a lot of these programs already so we will just run the HTTP server example. So this is gonna start the client, start the server, send a request from the client, get back the result and as you see, so there's no smoke and mirror magic here, we can make modifications to the Ruby code, we don't have to recompile the Go because all the work that we're doing on our side is happening in Ruby, so we could say hello RubyConf, rerun this code and Go will serve our new HTTP page for us and all the serving of the HTTP is happening from Go. So that's HTTP stuff, you can also do, you can also do fun stuff with arrays, so you can mutate arrays, so you can reverse arrays, you can do all this fun stuff in Go that you would do so you could take an array and writing Go code, just like regular Go code, you would sort of reverse an array maybe not exactly like this but you would reverse an array something like this, you would return it back to your Go library as if it was just regular Go code and we can have this turn it into a mutable array on the Ruby side and so if we were to run this example so we get the output of we reverse an array, we have a mutable string array at the second example, we can basically modify the array, add an element to the end, maybe change one in the middle and then we can also do the same thing with integers and that code down here is as you see basically we're passing in an array over here, this list in Go gets turned into an array in Ruby and then we print the result back inside of Go and you can see that Go has the modified Ruby array. Same thing for mutating lists and you can see down here we have an array of integers, an array of integers, we multiply every integer by five and then we print the result from the Go side and we see that we were able to mutate it. So a bunch of things that you can do with Gorb, lots of fun things you can do with Gorb, let me talk more about how it sort of works. So we saw that basically what it does is it generates wrappers for you and basically how that works is you give Gorb a bunch of Go source files, Gorb will then turn those source files into wrapper functions that we saw, that all that messy CRuby stuff. You will then automatically, Gorb will automatically then throw that stuff into Go and compile that stuff using Go's CGhost support so it'll compile a dynamic library for you and all that stuff will happen and it'll generate a native extension which Ruby can then load. The important part is really just this part because the rest of it sort of happens, that's all just Go stuff that happens as you would build a dynamic library normally with Go, that's all just regular Go stuff. So the important part is sort of the part where we generate these wrapper files. So the way that Gorb generates these wrapper files is sort of this little four step compiler type process. It's sort of a transpiler if you would think, it takes one type of code, it turns into another type of code. So it's kind of a compiler tool. And in being a compiler tool, the first step is always to parse the AST or the abstract syntax tree. This is typically something that a compiler will use to sort of understand the pieces of your program. So it'll take the text of your Go code, it'll turn it into this big tree that has all the little bits of your program inside this tree that it can iterate over. And we will take that AST and actually use it to read the source code. So we take that AST and we basically read, find all the functions, find all the structures and keep track of everything. So we have a structure up top which has three attributes and we keep track of those attributes. Again, as I mentioned, because Go is separating these data types from the functions, it's very easy for us to detect what is an attribute, what's a function and separate them out. So we go in and we read all the functions, we read all the structs and we create our own internal representation. So we keep track of all the attributes, all their types, all the functions, all the arguments, all the return types, everything we need to generate code. And then we go ahead and we just spit out a bunch of Go code. And this part is just basically using templates, sort of like ERB style templates. We basically just generate a function. You could sort of see little bits and pieces of the structure in the outputted code. So we had three attributes at the top of RGB and you could see here that this is part of the code that generates the attributes for the RGB attributes, for the RGB fields and in Go. And so you have the setter up top which basically sets the value and then you have the G getter and setter functions that basically get and set the value inside of Ruby. And you could see little bits and pieces of the RGBG and the int value conversion happening. So that's sort of how Gorb sort of code gens all this stuff for you. Once we have those Go files, they can be compiled into native extensions. You just have to do like go build dash tees. There's a couple of command flags but it's mostly just go build and it'll give you a native extension that you can then load directly into Ruby. So Gorb is not a complete implementation. It has plenty of limitations and flaws. One of them is the limited concurrency support. That's not exactly Go Gorb's problem but it's the fact that Ruby has a gil, the interpreter lock as Mass talked about. Basically what happens is you can do Go routine, you can do concurrency stuff inside of Go. You can do all this fancy multi-threading stuff inside of Go but unfortunately because of the gil you can only talk to Ruby from the main thread. So anything that you wanna do, anything that you wanna send back to Ruby has to happen in a synchronized main thread and unfortunately that can be a limitation to a lot of Go programs. So that's definitely a limitation. It only has basic block support. So you can't really do funky things with blocks in Ruby. You can store them as prox and use them later. That's not really supported by Gorb. There's a lot of weirdness around how blocks might raise exceptions which isn't necessarily supported by Gorb. There's also mutable array types aren't necessarily supported because Go is a typed language. Every single type is a separate type so array of strings is different from array of integers and Gorb doesn't support the entire gamut of every single possibility yet. So what actually happens is it only supports arrays of strings, integers and bools and anything else you're kinda on your own right now. It doesn't have mapping from hashes in Ruby to maps in Go or vice versa. It can't really export standard library types so if you had this HTTP server and you wanted to return the entire HTTP.request.response struct from Go into Ruby Gorb can't really do that automatically. If you wanted to do that you can wrap it inside your own Go struct that took bits and pieces of that response struct and return them to Ruby separately but it can't really return the full thing. That's something that could be added just it doesn't support it yet. And similarly there's no great cross package support story so you can use stuff across packages but it's hard to export them necessarily to Ruby. And it's fine that there are these limitations because the goal here wasn't to write a Go of Ruby to Go wrapper type thing. The goal here for me was I started this just trying to learn more about Go. So I was just writing a toy and I ended up with a toy. So it is a toy, you can play with it. There's definitely stuff you can do with it but it really is at the end of the day just a toy. And in being a toy I figured I'd mentioned some real world alternatives. If you're interested in this kind of thing of like writing fast code inside of Ruby. There's stuff like Swig which stands for simplified wrapper interface generators. This is basically like a C++ answer to this problem. Swig supports converting C or C++ libraries into Ruby or Perl or Python. It has support for a ton of different languages. It basically does the exact same thing but for C libraries. So if you were writing a C library you can do this. You can use Swig. I was writing a Go library so Swig doesn't really apply here but there are ways you can make that work if you wanted to. There's FFI which stands for foreign function interfaces. You might have seen foreign function interfaces in places. It's sort of a way to dynamically call out to functions in Ruby to native dynamic libraries like the MySQL gem might have FFI bindings or other gems. It would be great to use something like FFI but the problem for me is that it's just a no go because it has no go involved. I wouldn't be actually writing go code. I would just be writing Ruby code and that kind of defeats the purpose for at least for what I was trying to do. And the point here is that it wasn't really about it wasn't really about getting an end-to-end solution for me. It was really about practice. So I looked at this tool for me as just a way for me to write a toy and sort of gain experience. And for me, writing toys, playing with toys, using toys is really about practicing your craft and getting better and learning. And that's sort of what I think is important with these tools and toys that we work on is all these things sort of teach us something about the language we're learning or the tool or the framework we're learning. And at least for me, when I'm writing a Hello World program in a new language or a new framework, I'm usually looking at automation tools as my answer for a Hello World program. I'll automate some email sending thing or some alarm clock system or that's what I usually look to. People occasionally ask me sort of like what I'm learning rust in my spare time and I'm doing it outside of work so I don't really have any like real world things to do with this programming language. What should I write? And for me, the thing that always comes back is sort of automation. You should find some repetitive task in your day-to-day life, an email that you always have to send your boss, an email that you always have to send your friend, a Facebook link that you'd never want to click or something like that. And find a way to sort of automate that and learn your tool by, there's your problem right there, just automate it. For me, automation is kind of our job description as software developers. I feel like our goal is just to automate things and figure out how to make things work better without us actually doing anything. So there's always a use case for automating something and not only will you actually be able to learn a new language or a new tool or a new framework, but you'll also be able to be better at your craft as a software developer because automation is really important to pretty much everything we do. So you're always picking up different techniques, different things. You're learning a bunch of different things besides just the language or framework that you're learning. It's kind of like two birds, one stone type thing. So you can, I would recommend that if you're in my situation where you're just trying to learn this new language, practice automating something. Just take some problem, go automate it in that language. You can automate anything. You can automate, if you like this talk and you like the idea of this talk, you could take Lua, plug it into Ruby. You could take Ruby, plug it into Python if you want to. You could pick any language really. You don't even have to pick a language. You could pick anything and plug it into any other thing. You could do the opposite. You can even automate cooking if you wanted to. The point here is that take tools, take toys, use them for practice. Create something, play with toys, play with tools. Use them as practice. That's kind of like what the reason for me was for me for creating a tool called Gorb. So thank you for letting me share that with you guys. Does anyone have any questions? Yes. How do channels in Go get represented in Ruby? So Gorb does not export channels. The idea here would be that you would sort of abstract the channel either through a block that would get called and then sent to a channel internally or just a function that would send to a channel and then return back to Ruby and then you would do sort of the work behind the scenes. There is the problem with, again, concurrency. You can't really do complex concurrency stories inside of Ruby just because of the GIL. So even if I did export channels directly to Ruby, which is theoretically possible, you could just export a channel as some kind of object that you can send to. It still would be complex because you couldn't actually do any weird concurrency stuff anyway, so it's something that could be supported but it's complex to actually support in a real world case. You'll most likely, as Matt said, end up crashing Ruby by trying to do something weird with concurrency in Ruby, at least from the main thread in Ruby. You can definitely do stuff behind the scenes. You can do the good use case, sorry, the good use case for that is like workers. So you can spawn a worker thread or that runs in the background and then every time Ruby asks, it does a block and pulls and pulls back but it's still processing in the background in a separate thread. So that would be kind of a good use case for concurrency but yeah, it kind of gets hairy when you're pulling that abstraction back into Ruby. Yeah, I would say a worker queue would be a great example of that. So you basically pump stuff in from the Ruby side, just like have a method that just takes an argument like a string, push that into the channel as an abstraction. Ruby doesn't even know that it's a channel. Gets processed inside of a queue in the background and then Ruby either can pull or it can just call a block back when it's done or something like that, yeah. Any other questions? Yeah, so Go has a runtime but Go's runtime is embedded into the binary. So sorry, the question was when you compile a Go program, the SO file is Go involved or is it just C code at that point? And the answer is there is a Go runtime that's embedded in the binary so it's not actually calling out to like Go on your system or anything, you don't need Go on your system. It is calling Go code. There is like a Go runtime and there's garbage collection happening and there's other sort of stuff that's happening in the background in that process but that's all sort of embedded in the binary. So you end up with this SO file that is effectively machine code but it includes a lot of Go stuff in it as well. Sorry, sorry, what's the question? Yeah, so when things break in the Go wrapper code or inside of the runtime is it difficult to track down problems? That's the question. So yeah, I mean, depending on the problem, if it happens at compile time, it's usually pretty easy. You'll get like some weird exception that just panic, Go will just panic and say I can't generate this file and there's a lot of, as you mentioned, there's a lot of edge cases where just Gorb doesn't handle. There's obviously work to be done to like make it handle it more appropriately but it'll panic and in that case, it'll just panic. In the Ruby case, it'll probably just like do the Ruby style C crash where it just dumps the entire stack for you, like the C stack and it'll say like something bad happened and in that case, it's harder to track down because you don't really have like what happened. So it can occasionally be complicated to track down but that's something that can be improved. Any other questions? Cool, thank you guys very much.