Borgo is a statically typed language that compiles to Go
github.com
github.com
Some of the design decisions seem to me to be a bit more driven by being Rust-like than addressing Go's thorns though. In particular, using `impl` to define methods on types (https://borgo-lang.github.io/#methods), the new syntax for channels and goroutines (https://borgo-lang.github.io/#channels), and the `zeroValue()` built-in (https://borgo-lang.github.io/#zero-values-and-nil) seem a bit out of place. Overall though, if I had a choice, I would still rather write Borgo by the looks of it.
The idiomatic way to write Go discourages you from robust error handling; return an opaque error, which callers will probably deal with as a string (or bubble-up as much as possible) because knowing its potential concrete types requires either reading source code or having documentation available (as the idiomatic function return type won't tell you anything about it). The path of least resistance only goes as far as forcing you to acknowledge there might be an error, it doesn't help you make good decisions about how to deal with it.
I think without also having to learn about lifetimes and dealing with async that this still presents an attractive trade-off for people who want to be quickly productive and don't care as much about garbage collection.
Go was designed to be simple enough that developers can’t write code too complicated for others to read, with a particular eye towards junior devs (and ops/infra people, I think).
This usage of sigils for error handling and union return types is very cool and very expressive, but also going to confuse the shit out of your new devs or infra people. It’s just not a good fit for what Go wants to be.
I’m even sympathetic to the idea that generics are similar, though personally I think the alternative to generics is code generators, which are worse.
Anecdotally, I recently wrote some Go code at work (infra team) that used generics, and I had to look outside my team to even find someone that felt comfortable reviewing generic Go code. I see a fair bit of code using interface{} or any that would be much simpler and better with generics.
From my experience, things I think Go could really benefit from, like I believe it has benefited from generics:
* A way to implement new interfaces for existing types and type constraints for custom types, like `impl Trait for T`. This would obsolete most uses of reflection in the wild, in a way generics alone haven't. This isn't about syntax, it's about an entirely different way to associate methods to types, and with both Go and Rust being "data-oriented" languages, it's weird that Go is so limited in this particular regard. It has many other consequences when combined with generics, such as ...
* Ability to attach receiverless methods to types. Go concrete types may not need associated methods like "constructors", but generic types do, and there's no solution yet. You can provide factory functions everywhere and they infect the whole call graph (though this seems to be "idiomatic"), or make a special method that ignores its receiver and call that on a zero instance of the type, which is more hacky but closer to how free functions can be resolved by type. There's no reason this should be limited to constructors, that's just the easiest example to explain, in Rust associated methods are used for all kinds of things. Speaking of which...
* `cmp.Ordered` for custom types. Come on people. We shouldn't still have this much boilerplate to sort/min/max custom types, especially two full years after generics. The new `slices.SortFunc()` is the closest we've ever come, and it's still not associated with the type. We would basically get this for free if both of the above points were solved, but it's also possible we get something else entirely that solves only ordering and not e.g. construction or serialization.
* Enums, especially if the need for exhaustiveness checking could be balanced with Go's values of making code easy to evolve later. When I need them, I use the `interface Foo { isFoo() }` idiom and accept heap boxing overhead, but even the official `deadcode` analysis tool still to this day does not recognize this idiom or support enough configuration to force it to understand. The Go toolchain could at the very least recognize idioms people are using to work around Go's own limitations.
If we had solutions to these problems, I think most Go folks would find enough value in them that they would still be "Go". In fact, I think people would have an easier time consolidating on a new standard way to do things rather than each come up with their own separate workarounds for it.
This is where I feel "The code I write, is going to look like the code written by most other on my team" the least, because that's only true when a Go idiom has some official status, it's not nearly as true for workarounds that the Go team has not yet chosen to either endorse or obsolete.
Obviously it already benefits from enums.
type E int
const (
A E = iota
B
C
)
Which, once you get past the superficiality of syntax, is identical to, say, what you find in C. enum E {
A,
B,
C
}
Enums are just a numbering mechanism, after all. No different than hand numbering constants, but without the need to put in the manual effort. Enums kind of suck, though. Are you sure any language actually benefits from them (as compared to better alternatives)?> especially if the need for exhaustiveness checking
It is true that gc doesn't make any effort to determine how the enums are used, but if it were to it would be a warning like as is seen in many C compilers. As enums are values, not a type, it's not a fault to use them "inappropriately". While it may be all fine and dandy to add such warnings to gc, the Go team has taken a hard line stance that warnings have no place in the compiler. Of course, the external analysis tools like you speak to can still be used to provide these warnings for you. All the information you need is there.
But it seems what you really want is a more expressive type system – specifically sum types. Then you would be able to describe how you expect identifiers to be used without resorting to using generated number values as placeholders. Enums are just a hack to work around a limited type system anyway. If you are going to improve the type system in order to gain improved compilation constraint, you don't need enums anymore.
Rust doesn't have enums and nobody has ever noticed. Hell, many are even somehow convinced it has enums – but it does not. It provides tag-only unions (with, optionally, fully tagged unions) instead. Seemingly that is what you really want in Go as well. And fair enough. It is undeniably the better solution, but at the cost of being more complex to implement.
As someone who has the "Rust bias", I feel like it's a bit of an open secret that a _lot_ of Rust developers don't actually need the extreme low-level performance that it offers and use it more because of the quality of life things (including some of the features in Borgo, but also tooling like cargo, rustdoc, etc.). I've said for a while that a language that focused on this sort of "developer experience" but used a GC to be a bit more flexible would probably be enough for a large portion of Rust developers, and pretty much everyone Rust developer I've talked to agrees with this (even if they aren't in the portion that would use this).
It's also pretty common for me to see people asking why someone would use a low-level, C++ language for something like web development, and I think the explanation is pretty similar; people like a lot of what Rust has to offer besides the low-level C++-like semantics, but there isn't something higher-level that offers enough of those features yet. Probably the language that would come closest to this is OCaml, but stuff like the documentation and "multiple preludes" are exactly the kind of thing that add friction to getting up and running, which is something Rust has invested a lot of time into reducing.
Why then would Go not fit? It prioritizes developer experience (documentation, automatic formatting, etc.) with a GC
Things like this might be small, but they add up, and at the end of the day, the frustrations that I encounter when writing Rust are smaller and less frequent than the ones I've had writing Go. Obviously stuff like this is subjective, and there's no way to make a language that satisfies everyone. I think there's empirical evidence that there's an audience for a language that fits the niche I describe that Go doesn't fill though.
You're right that judging on the engagement of this post and others in the past there must be a big appetite for a language somewhat like Go but with fundamental differences. It's really quite interesting how Go seems to be so polarizing, they really nailed some things and really missed on others.
I think Swift would tick most of those boxes, it’s a shame it hasn’t really picked up outside Apple-land.
It can be a horribly complex language, but day-to-day it’s very nice to write.
Having worked on multiple very large Go codebases with many engineers, the lack of actual enums and a built-in optional type instead of nil drive me crazy.
I think I'm in love.
Edit: Looks like last commit was 7 months ago. Was this abandoned, or considered feature complete? I hope it's not abandoned!
Enumerated types are simply named integers in most languages, exactly the sort you get with const / iota in Go: https://en.wikipedia.org/wiki/Enumerated_type
Rather than the tagged union which the word represents in Rust, and only Rust. Java's enums are close, since they're classes and one can add arbitrary behaviors and extra data associated with the enum.
It remains a strange choice to refer to this as the true enum, actual enum, real enum, as has started occurring since Rust became prominent. If that's a meaningful concept, it means a set of numeric values which may be referred to by name. This is the original definition and remains the most popular.
From a PL theory perspective, enum denotes an enumerable set of values within a type. It just happens that sums slot in well enough with that.
I didn't read GP as saying "Actual enums are what Rust has", I read it more as "Go doesn't have actual enums", where "enum" is a type that is constrained to a specified set of values, which is what all mainstream non-Rust languages with enums call "Enums".
I mean, even if Rust never existed, the assertion "Go doesn't have actual enums" is still true, no?
"Does Go have enumerated values" seems much like "does Lua have objects". Lua doesn't have `class` or anything like it, it has tables, metatables, and metamethods. But it makes it very easy to use those primitives to create a class and instance pattern, including single inheritance if desired, and it even offers special syntax for defining and calling methods with `:` and `self`. If I had to deliver a verdict, I would say that the special syntax pushes it over the line, and so yeah: with some caveats, Lua has objects.
Same basic thing with Go. One may define a custom integer type, and a set of consts using that type with `iota`, to get something which behaves like plain old small-integer enums. It's possible to undermine this, however, by defining more values of this type, which makes this pattern weaker than it could be, but in a way which is similar to the enums found in C.
Ultimately, Go provides iota, and making enums is the intended purpose of it. If you search for "enums in Go" you'll find many sources describing an identical pattern. So, like `self` and `:` in Lua, I'd say that `iota` in Go means that it has an enumerated type.
But if someone wanted to say "Go doesn't even have enums, you have to roll your own, other languages have an enum keyword", I have a different opinion than that first clause, but there's nothing factually wrong with any of it. I find this sort of "where's the threshold" question less interesting than most.
You could look at Lisp. It's kind of the opposite of Go in this regard. You can use whatever paradigm you like, generate new code on the fly, use types or leave them. It even allows you to easily extend the language to your taste, all the way down to how code is read.
But Lisp might violate your set of absolutes.
That said, suggesting adding exceptions to Go is about as reasonable as adding a GC to Zig. How much effort would you spend arguing against someone bringing that up as a serious proposal?
It has its flaws, but the latest version (Scala 3) is really really good. The community is open to different styles of programming - from using it as a "better Java" to "pure functional programming like in Haskell".
It is rather the non-believers in exception handling who are the lunatic fringe that benefits from a healthy dose of ostracism.
func try(fn func()) { fn() }
func catch(fn func(any)) {
if v := recover(); v != nil {
fn(v)
}
}
func throw(v any) { panic(v) }
func fail() {
throw("Bad things have happened")
}
func main() {
try(func() {
defer catch(func(v any) {
fmt.Println(v)
})
fail()
})
}
Sorry.The syscall/js package [0] throws panics if something goes wrong, rather than returning errors.
Go already has try/catch exceptions. We just don't use them most of the time because they're a really bad way of handling errors.
if err != nil {
return err
}
repeated all over the place, is the epitome of productivity!If you do naked if-err-return-err, you are likely doing error handling wrong.
In JS, for example, people don‘t even know which functions could throw exceptions, and just ignore them, most of the time. Fast to write and looks nice, but is horrible quality and a nightmare to debug.
But in fact, there probably exists a minority of developers who somehow had Go foisted upon them, and who would would like it to have basic features like exceptions.
Why do you think so? Maybe I'm an odd case, but my main use case for enums is for APIs and database designs, where I want to lock down some field to a set of acceptable values and make sure anything else is a mistake. Or for state machines. Error handling is manageable without enums (but I love Option/Result types more than Go's error approach, especially with the ? operator).
The thing is, these don't add much on their own. You'd have to bring in pattern matching and/or a bunch of other things* that would significantly complicate the language.
For example, with what's currently in the language, you could definitely have an option type. You'd just be limited to roughly an api that's `func (o Option[T]) IsEmpty() bool` and `func (o Option[T]) Get() T`. And these would just check if the underlying point is nil and dereference it. You can already do that with pointers. Errors/Result are similar.
A `try` keyword that expands `x := try thingThatProducesErr()` to:
x, err := thingThatProducesErr()
if err != nil {
return {zero values of the rest of the function signature}, err
}
Might be more useful in go (you could have a similar one for pointers).* at the very least generic methods for flat map shenanigans
Then what you are really looking for is sum types (what Rust calls enums, but unusually so), not enums. Go does not have sum types, but you can use interfaces to archive a rough facsimile and most certainly to satisfy your specific expectation:
type Hot struct{}
func (Hot) temp() {}
type Cold struct{}
func (Cold) temp() {}
type Temperature interface {
temp()
}
func SetThermostat(temperature Temperature) {
switch temperature.(type) {
case Hot:
fmt.Println("Hot")
case Cold:
fmt.Println("Cold")
}
}Then wrap appropriately. Something like sqlc will actually generate everything you need.
type Option1 struct { ... }
type Option2 struct { ... }
type MyEnum interface { Option1 | Option2 }
var myValue MyEnum // currently not legal Go
That doesn't solve all the use cases for enums / sum types, but it would be useful.With that I mean fundamental and fool proof functions for to/from a string, to/from an int, exhaustive switch cases, pattern matching, enumerating.
Seems like something that wouldn’t be too hard but everybody always fails on something.
There's not much you can do with an enumeration. It's just something that counts one-by-one.
A useful tool when you have a large set of constants that you want to number, without having to manually sit there 0, 1, 2, 3... But that's the extent of what it can offer.
> With that I mean fundamental and fool proof functions for to/from a string, to/from an int, exhaustive switch cases, pattern matching, enumerating.
While a programming language may expose this kind of functionality, none of these are properties of enums. They are completely separate features. Which you recognize, given that you are able to refer to them by name. Calling these enums is like calling if statements functions because if statements may be layered with functions.
What people want "the ability to express enums with an associated value", I think we should invent a new term.
type E int
const (
A E = iota
B
C
)
It's funny how people who have clearly never even looked at Go continually think they are experts in it. What causes this strange phenomena?They can have values like sum types, or not.
So when I looked at Go for the first time, the error handling was one of the many positive features.
Is there any good reason for wanting try/catch other than being lazy?
sounds good on paper, but seeing "if err!=nil" repeated million times in golang codebases does not create positive impression at all
Okay, but other than exceptions, whats the alternative?
It's the best strategy for short running programs, or scripts if you will. You just write code without thinking about error handling at all. If anything goes wrong at runtime, the program aborts with a stacktrace, which is exactly you want and you get it for free.
For long-running programs you want reliability, which implies the need to think about and explicitly handle each possible error condition, making exceptions a subpar choice.
That may be superficially true but don’t forget our brain is structured to optimize every repetitive work or some boilerplates, we can basically use “strcpy” and “string_copy” we are so used to all these that even if repeated a billion times it can be processed fast
In a hot path it’s often beneficial to not have lots of branches for error handling. Exceptions make it cheap on success (yeah, no branches!) and pretty expensive on failure (stack unwinding). It is context specific but I think that can be seen as a good reason to have try catch.
Now of course in practice people throw exceptions all the time. But in a tight, well controlled environment I can see them as being useful.
This is true but the branch isn't taken unless there's an error in Go.
Given that the Go compiler emits the equivalent of `if (__unlikely(err != nil)) {...}` and that any modern CPUs are decently good at branch prediction (especially in a hot path that repeats), I find it hard to believe that the cost would be greater than exceptions.
You generally need to skip all lines that the exception invalidates. That's why it's a block or conditional.
1. err != nil, nondefault return value
2. err != nil, default return value
3. err == nil, nondefault return value
4. err == nil, default return value
when often what you want to express only has two: either you return an error and there's no meaningful output, or there's output and no error. A type system with tuples but no sum types can only express "and", not "or".
Try/catch is super confusing because the catch is often far away from the try. And in Python I just put try/catch around big chunks of code just in case for production.
I think Go is more stable and readable because they force you not to use the lazy unreadable way of error handling.
Enums I honestly never used in Go also not the not-type-safe ones.
But I'm also someone who used interfaces in Go maybe I think 4 times only in years and years of development.
I just never really need all those fancy things.
For example, and option type with enums combined can ensure return values are checked by providing a compile time error if a case is missing (as expressed in the first few examples of the readme).
But they decided not to add enums because it conflicted and overlapped too much with interfaces.
I just want to add "my" experience that personally, yes maybe you can argue enums are nice, but I never missed them in Go.
I personally agree with the Go team on how they argue and for me it would be a step back if they listened to the herd that does not take all sides of the story into consideration but just keeps pushing enums.
Try/catch is just a really bad thing all "hacky solution" alarm bells go off for me if you want to change error handling to giant try/catch blocks.
But you ruined it with "fancy things" which shows offhand disregard and disrespect.
A question like "what do you need these features for?" would have been a better contribution to the forum.
I also wanted to add that I used inheritance only ONCE in all my years of writing Python in all other millions of lines of code inheritance was not the best solution.
This is my daily struggle as a CTO. People using waaayy too many "fancy" features of languages making it totally unreadable and unmaintainable.
It's their ego they want to show off how many complex language features they know. And it's ruining my codebases.
- braid: https://github.com/joshsharp/braid
- have: https://github.com/vrok/have
- oden: https://oden-lang.github.io/
I'd love to be able to use a bit more type-y Go such as Borgo, and have a Pythonesque dynamic scripting language that latches onto it effortlessly.
Dynamic typing is great for exploratory work, whether that's ML research or developing new features for a web app. But it would be great to be able to morph it over time into a more specified strongly typed language without having to refactor loads of stuff.
Like building out of clay and firing the parts you are happy with.
Could even have a three step - Python-esque -> Go/Java-esque -> Rust/C++esque.
We do exactly that with Common Lisp. It compiles to different languages/frameworks depending on what we require (usually sbcl is more than enough, but for instance for embedded or ML we need another step. All dev (with smaller data etc) is in sbcl so with all the advantages.
For learning cl, the reddit lisp subreddit is good and has the current best ones on it. Lately there is a guy making a gui (clog) who is doing good work for spreading general lisp love by making it modern. Including tutorials. And there are others too.
Also types are self documenting to an extent. Could be helpful for a shared codebase. Again Python just now getting round to adding type definitions.
At the end of the day good tooling/ecosystem and sheer developer hours is more important than what I'm suggesting but it would be nice anyway. I dream about cool programming languages but I stick to boring for work.
The larger problem is building an ecosystem and a stdlib that's written in python, not C. Use ffi or similar instead of C-API.
The rest of the readme focuses on the delta between Go and Borgo. It doesn't say much about the delta between Borgo and Rust.
I think the delta there is mainly no lifetimes/ownership?
Also: No `?` operator
I wonder if Borgo's compiler messages are as nice as Rust's/Gleam's, though.
Ymmv, you might be surprised if you actually bothered to benchmark. Depending on the workload, either JS or erlang can ultimately turn out on top.
They're all optimized to a degree that each has a niche it excells at and leaves the others in the dust.
even with heavily scewed benchmark like techempower fortunes (https://www.techempower.com/benchmarks/#hw=ph&test=fortune&s...) you end up with JS getting ahead of Go with raw requests. And not just slightly, but by 1.5 times the throughput.
In other benchmarks, Golang does indeed win out with similar or even bigger advantages... so the only thing you can ultimately say is ... that it depends. Its a different story if you chose other languages though. But JS, Golang and Erlang are all extremely optimized for their ideal usecase.
Scroll down to the "nodejs" entry for a more realistic comparison.
I'm not saying that JS is "just as fast as golang" generally. My argument is specifically that it's optimized to a degree that there are cases in which JS, an interpreted language, does end up on top.
The same applies to erlang and it's optimization for efficient concurrency.
On average you'll likely get better performance with go, but depending on the workload the results can differ
Structure privacy, OTOH, does. Count me in as someone who really enjoys the case-based approach. It’s not the only one which could work, but it does work.
The moment you integrate with a third party your US centre zip_code field is suddenly coming over the wire as postCode. The conversions are going to go on, at least in go I can define all of that conversion with ease in one place.
I don't disagree, the problem I have with it is, I have to pay for that up front and have to factor it into my design immediately. This also combines with the fact that the namespace is very flat with no heirarchy, so, choosing good public names is something I feel like I spend way too much time on.
Go is the only language that causes me to pull out a thesaurus when trying to name methods and struct members. It's kinda maddening. Although, after going through this exercise, I end up with code that reads like English. I just wish I could refactor my way into that state, rather than having to try to nail it up front.
Procrastination looms. :o
I'm guessing you are talking about something else entirely, like, perhaps, decoding JSON into a struct using reflection and encountering a situation where the field names didn't match? Indeed, implicitness can bite you there. That's true in every language. But, then again, as you prefer explicitness why would you be using that approach in the first place?
I forget what it was, but basically my code wasn’t working the way I thought it should and it was solely due to a lowercased struct field. It happened twice where I spent at least a little while trying to figure it out.
And yeah I would guess that I tried to compile. Would be very dumb if I hadn’t although wouldn’t be the dumbest thing I’ve ever done
I never wrote ObjC but didn’t they use + and - (and nothing) as visibility modifiers?
Frankly if they insisted on visibility being part of the name, I would have preferred they go with the age-old C++/ancientPHP tradition of using a _ prefix for private members.
The latter will be there in one of the future versions and is in an active design phase, which luckily focuses on tagged-union implementation strategy.
With that said, you can already easily use one of the Option/Result libraries or write your own structure - switching on either is trivial (though you have to sometimes choose between zero-cost-ness and convenience).
It already has struct generics, iterator expressions (LINQ), switch pattern matching, good C interop and easy concurrency primitives (no WaitGroup nonsense, also has Channel<T>). Oh, and also portable SIMD, C pointers and very strong performance in general.
* True as in proper tagged unions with either a tag or another type of discriminant and aliased layout, instead of tag + flattening all constituent parts into a one jumbo struct. Or making it an opaque pointer to a box (like Java does, or C# if you go inheritance/interface route). These don't count. I'm curious about Borgo's lowering strategy for enums, but given Go doesn't have those, I'm not holding my breath and expecting something like F# struct unions at best.
It isn't without a whole list of caveats if you're used to Go's way of doing things though. See <https://learn.microsoft.com/en-us/dotnet/core/deploying/nati...> for details.
For others wanting to play along at home:
$ docker run --name net8 --rm -it mcr.microsoft.com/dotnet/sdk:8.0-jammy-arm64v8 bash -c '
cd /root
dotnet new -d -o console0 console
cd console0
dotnet publish --nologo --self-contained -v d -r osx-arm64 -o console0-darwin-arm64
sleep 600
'
although it didn't shake out $ docker cp net8:/root/console0/console0-darwin-arm64/console0 ./console0
$ ./console0
Killed: 9
I tried with and without --self-contained and the biggest difference was that self-contained emitted a bazillion .dll files and without just emitted the binary. I case the context isn't obvious, $(dotnet new console) is a skeleton for the infamous WriteLine("Hello, World") without doing crazy whacko stuffGenerally, there are three publishing options that each make sense depending on scenario:
JIT + host runtime: by definition portable, includes slim launcher executable for convenience, the platform for which can be specified with e.g. -r osx-arm64[0].
JIT + self-contained runtime: this includes IL assemblies and runtime together, either within a single file or otherwise (so it looks like AOT, just one bin/exe). This requires specifying RID, like in the previous option, for cross-compilation.
AOT: statically linked native binary, cross-OS compilation is not supported officially[1] because macOS is painful in general, and Windows<->Linux/FreeBSD is a configuration nightmare - IL AOT Compiler depends on Clang or MSVC and a native linker so it is subject to restrictions of those as a start. But it can be done and there are alternate, more focused toolchains, that offer it, like Bflat[1].
If you just want a hello world AOT application, then the shortest path to that is `dotnet new console --aot && dotnet publish -o {folder}`. Otherwise, the options above are selected based on the needs either via build properties or CLI arguments. I don't know which use case you have - let me know if you have something specific in mind ("Just like in Go" may or may not be optimal choice depending on scenario).
[0] https://learn.microsoft.com/en-us/dotnet/core/rid-catalog
[1] https://github.com/bflattened/bflat (can also build UEFI binaries, lol)
If were up to me we'd be using a language with a better type system :)
[0] https://github.com/shuebner/ClosedTypeHierarchyDiagnosticSup...
- A proposal for sum types by a Go team member: https://github.com/golang/go/issues/57644
- The community proposal with some comments from the Go team: https://github.com/golang/go/issues/19412
Here are some excerpts from the latest Go survey [1]:
- "The top responses in the closed-form were learning how to write Go effectively (15%) and the verbosity of error handling (13%)."
- "The most common response mentioned Go’s type system, and often asked specifically for enums, option types, or sum types in Go."
I think the problem is not the lack of will on the part of the Go team, but rather that these issues are not easy to fix in a way that fits the language and doesn't cause too many issues with backwards compatibility.
One of the proposals you linked has been raised in 2017 and it is still open with "No one assigned"; same fate for the other item. That doesn't inspire confidence in terms of Go team treating these things as top priority.
I think stuff developers are moaning about the most should be top priority but I guess that is just my simpleton thinking.
> I think the problem is not the lack of will on the part of the Go team, but rather that these issues are not easy to fix in a way that fits the language and doesn't cause too many issues with backwards compatibility.
They have made many changes to the language, some significant ones like Generics (which I would assume was also not an easy problem to solve) while they have largely left the elephant in the room unaddressed i.e. error handling - and the developers deal with that one on a daily basis and I would wager a lot more frequently compared to Generics. If I had to gauge their priority, I would go by where they they are putting their money instead of surveys and proposals. And their priorities seem to be different from what the populace is asking. And that is my point.
But introducing a new language is a scary thing.
rustaceans “I love the Rust syntax so much I want it in Go too”
I have no issue with the syntax but I think the chance of uptake would be considerably improved if the syntax were as close to Go's as reasonably possible. That's because I estimate Go programmers to be a better target for this than Rust programmers, but maybe I'm wrong.
https://www.reddit.com/r/rust/comments/j2l9v9/revisiting_a_s...
Rust’s syntax looks alien to people who are not familiar with it, but the syntax itself is fine.
Some users also blame Rust’s syntax for being complicated when they actually struggle with Rust’s semantics, e.g. borrow checking wouldn’t be any less strict if Rust chose a less weird sigil for lifetime labels.
It appears to be a transpiler (consumes a Borgo and does the work to convert and emit a Go program as text):
https://github.com/borgo-lang/borgo/blob/main/compiler/src/c...
There aren't any such techniques. If one were to create an academic discipline to study "transpilers" and one to study "compilers", all you'd end up with is an identical bunch of techniques and analyses with different names on them. (A thing that sometimes happens when diverse disciplines study what turns out to be the same thing; see machine learning versus statistics for a well-known example.)
Even "compiling" to machine code isn't special anymore, because CPUs don't actually execute assembly anymore. They themselves compile assembly into internal micro-ops, which is what they actually execute. So compilers don't even compile "machine language" anymore; it's just another target. This also dodges "is it a 'compiler' or a 'transpiler' if it targets WASM?", which is good, because there is no value in that question on any level.
2. Transpilers don’t typically optimize code, leaving those transformations to the compiler of the target language.
3. Compilers will typically have an internal representation (SSA) which they operate on to optimize. Transpilers typically operate on the AST (because they don’t need to do any but the most trivial optimizations).
There are exceptions to the rules but these cover the majority of the reasons on why people make the distinction.
The average hobby compiler—regardless of target—doesn't optimize code and works directly on the AST because that's simple to get started with. Most hobby compilers also target some other language rather than LLVM or machine code because that's simple to get started with, so the result is that most transpilers are hobby projects that don't optimize. But there's no reason why a transpiler shouldn't include optimization steps that adapt the output to use code paths that are known to be fast, and a production-grade transpiler typically will include these steps.
You have provided some defining properties that might allow for distinction, but you have not given any reasons for why people make a distinction.
But perhaps we can suss it out. Given the statement "Borgo compiles to Go", what important information is lost that would be saved if "Borgo transpiles to Go" was used instead?
In the statement "XYZ is a compiler/transpiler", it does. It doesn't hurt to have a word that is more specific than others. Otherwise we should just refer to compilers as an "app" :)
But if you dig in, it will turn out that every single one of them has a different interpretation, quite often fatally so to whatever the task at hand is.
I mention this because my impression has been that the distinction between "transpiler" and "compiler" is that the latter is into some machine code and the former is not. I think if we could get people to sit down and very clearly define the difference we'd discover it is not as universal a definition as we think.
My personal favorite is when I say a particular term is not well defined on the internet, and I get multiple commenters to jump up and tell me off about how wrong I am and how well-defined the term is and how universal the understanding is, while each of them gives a completely different definition. As I write this it hasn't happened in this thread yet, but stay tuned.
Anyhow, the simple solution is, there isn't a useful distinction between them. There's no sharp line anyhow. Plenty of "transpilers" produce things like Python that looks like
def f000000001_bch(a0023, a0024, bf___102893):
__a1 = f000000248_BCh1(a0024, const_00012)
if __c_112__0:
f0000000923(__a1)
else:
f0000000082(__a1)
and it's really quite silly to look at what can be a very large process and make a distinction only in how the very last phase is run, and on a relatively superficial bit of that last phase too.It seems the term is not terribly useful even on its own terms... it is not as well defined as everyone thinks.
Ultimately, "compiler" isn't a bright shining line either... I can take anything and shade it down to the point where you might not be sure ("is that a 'compiler' or an 'interpreter'?"), but the "transpiler" term is trying to draw a line where there isn't even a seam in the landscape.
Something like "Assuming all concepts are universal to one's own peculiar definition"
Maybe "semantic egocentrism" could fit the bill?
No, a transpiler emits code that another system is meant to understand (often another compiler or interpreter). Whether a human can understand it or not is immaterial to the objective of transpiling.
What is meaningful about such a distinction?
What possible compiler target couldn't you write? Compilers are not exactly magic.
How come Godbolt is so popular? Inspecting compiler output?
Is GCC now officially a transpiler?
The middles all look very similar, though, which is where the heart of "compiler" comes from; that process of some sort of parsing and then transforming down to some other representation. This has a distinguishing set of characteristics and problems despite what frontends and backends get slapped on them.
And it's written in rust. Kinda unholy.
> And it's written in rust. Kinda unholy.
Agreed, it's like, do you really hate writing Go so much that you'll really write all that Rust to get out of it? Haha. Reminds me of the web frameworks which generated the JS so you didn't have to touch it, like GWT of old.
I'm sure it was a fun exercise to create Borgo, though.
My favorite transpiler is Haxe. It targets so many other languages, the surface area is impressive.
Tell us more about this compiler subset that does not produce binary output. What do they produce? Ternary? Qubits? Nothing?
Can anyone think of another example?
There's other successful but not "mainstream" languages that might count or semi-count, like Clojure targeting the JVM (though not Java) and being able to use Java packages, or ClojureScript targeting JavaScript.
clojurescript is fairly popular too.
All of that said - this train of thought lead me to discover AssemblyScript! https://www.assemblyscript.org/
I don't have a strong opinion on Result and Pattern matching - it seems nice, but I don't know if it adds much to the language. It is nice, but it may not be worth the complexity.
The error handling with ? is a no for me. I'd rather have something more like the go-errors/errors package in the standard library instead. This has been proposed before, and it was rejected for a good reason: it makes it too easy to be lazy and just bubble up errors instead of properly handling them.
Sometimes I think we focus too much and formalize on the first order tooling we use, language being one of them, while we neglect the layers upon layers of abstractions built on top of them. I wonder whether a meta-language could exist that would be useful in these upper layers. Not a framework that imposes its own logic. More of a DSL that can capture both business logic and architecture.
I find it conceptually compelling, I'm just surprised the target would then be in the GC'd, larger-binary'd direction. Like 'Java expressiveness with C simplicity, transpiles to Java'.
Perhaps 'just' because it's a lot simpler to just expand the target language slightly and then you only have to deal with mapping the new bits into implementation, it's less like writing a compiler for a whole new language?
It's even typical in a sense: C++ is a superset of C, that doesn't mean it has to compile to C, it compiles to LLVM IR or whatever.
use fmt
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn wtf() -> Coin {
return zeroValue()
}
fn main() {
let coin = wtf()
fmt.Println("zero coin:", coin)
}
Output: zero coin: {0}Why is this suddenly news, when by all appearances it's abandonware?
Because the submitter suddenly found it, and it was new to many others too? It's not a 'Show HN'.
Golang < Borgo < Rust
These features provide for more safety, and at the same time, they reduce productivity by forcing the developer to statically type everything.
The question is then why do we need to transpile to Go, a language with GC and slower than Rust?
If we already agree on super-safe static typing, why not just use Rust? Are there any libraries in Go that are not available or of worse quality in Rust?
You could of course write a bytecode VM in Golang but I guess that defeats the purpose.
Looks like abandoned though.
It's like one of those people who buys a massive over priced knife block with 48 knives in it that they never use.
Most go devs have lived through bloated java/php/python/ruby/js projects that become a pile of dependencies.
Go is to coding what brutalism is to architecture. Simple, functional, efficient. Dont build a massive dependency chain, dont build magic, repeating yourself is OK. Be an adult and deal with your errors (it's a feature)... That minimalist no bullshit language semantics that force you not to be lazy is a feature not a bug.
Your right, it does not. I will _ =: an error in a throw away script all the time.
I see that in a code review, in production code... Big red flag. This is a departure from an exception, that might be thrown in one place and handled far far away from the code you're looking at.
You can ignore errors in Rust.
There was a thread here the other day where a rust dev pointed out that "Rust is the language tokio ate"... https://nullderef.com/blog/rust-async-sync/
Rust is a lot of overhead when go is "good enough" for 99% of what needs to get done. That doesn't mean go is good for everything. I would still rather write Rust than C or C++ or bunch of other languages. Look at a project like Pingora, from cloud flair. Perfect Rust project, bad Go project. Rust is in the kernel, rust is what im looking at for a USB driver. I would not shove go in either of these places.
I did not post your parent comment.
Easy to learn: you can be productive in go in a day or two.
Strong standard library.
Complies to binary. (you dont need to drag a run time around) And this is fast!
Easy dependency management.
Linting is built in. (No arguing over tabs vs spaces)
First class testing. (and its fast)
"good enough" coding is very fast. You can mostly ignore performance and pick it up when and where you need it.
-----------------------
Go users tend to say "Idiomatic" a lot. Your not getting rails, there is no java like framework, and you really should NOT do the node js thing and stack tools to the sky. Minimalism, brutalism.
As an example: Most languages have tooling for dependency injection. Most go projects have dependency injection but dont use a library or framework or tooling to do it. Its just a bit of code (100 ish lines) that you end up writing as part of your bootstrapping, config or testing (depending on the project)....
There is no "one true" answer, and time spent bullying people over it, is entirely wasted.
It's a nice way to bootstrap ecosystem IMHO. No one wants to use a brand-new language without library support for common tasks.
> Go is simple and straightforward, but I often wish it offered more type safety. Rust is very nice to work with (at least for single threaded code) but it's too broad and complex, sometimes painfully so.
It seems clear that the author really likes aspects of both Go and Rust and desires something between the two. Check out the complexity vs type-safety illustration at the top of the page, with Borgo placed between the Gopher and the Crustacean just before the complexity curve gets steep.
They're basically building their personal ideal version of Go with inspiration from Rust.
Which of course begs another question but I won't be that Rust fanboy
Rust on the other hand drastically simplifies a lot of the complexity i dealt with in Go. However depending on the type of work, it's of course got plenty of complexity to dig into should you need it.
The challenge with Rust imo is to know where to use that complexity. Lots of rope to hang yourself with. On average i find myself with code that to me is simpler in Rust, because it's easier to reason about larger blocks of logic. However i still wouldn't ignore the extra rope of the whole language and call it "simpler" than Go.