I think a top priority for Rune should be simple interaction with other popular systems programming languages. Specifically, I think we should make Rune libraries easy and even fun to use from C, C++, and Rust. In the other direction, we are limited by the target language.
The C language designers managed to design a systems programming language that is easy to interface to from newer systems programming languages, like C++, and Rust, which all support the C linker. Rune is also designed to easily interface with C, but at a C library level. Unfortunately, C++ went down a path that more or less requires the world to use nothing but C++ to use C++ libraries. Consider C++ try/catch. The LLVM scheme for handling C++ exceptions is so convoluted and specific to C++ that probably no other language will ever support it. Carbon will most likely require Google-style StatusOr<T> everywhere to avoid the C++ exception handling system. That means Carbon may work well for Google's legacy libraries, but most of the rest of the world will not be so lucky.
C++ decided to throw/catch dynamically typed exceptions. The catch statements don't know what type was thrown, so the compiler builds C++-specific RTTI (run-time type information), which is compiler specific, making it impossible for any two C++ compilers to share libraries, other than g++ and clang++, because clang made it a top priority objective to interoperate with gcc.
In Rune, I think we need to take care to avoid such limitations to interoperability. In particular, for each language where we support strong interoperability, we should:
- Have flags to generate code in the target language to call functions compiled into our .so and .a libraries.
- Ensure things like exceptions are handled cleanly in extern interfaces.
For exceptions, I think this means we need to restrict what we can throw. I think Rust errs on the side of too little flexibility: only a single error type can generally be returned, requring SWEs to write more error translation code than I've seen in any other language. I think we can return errors that:
- Have an Enum class selection defined in an Enum class that even C can handle.
- All Enum error classes that a function can throw should be declared automatically in generated interfaces to supported languages.
For example, consider a function that sends out both HTTP requests, and Stubby requests. The HTTP errors could be (status: HttpStatus, msg: string), and the RPC errors could be (status: StatusCode, msg: string). To be able to return either, the generated code for C might look like:
enum ErrorCodeType {
HttpStatus = 1,
StatusCode = 2,
}
extern ErrorCodeType global_error_code_type;
union ErrorCode {
http_status;
status_code;
};
extern union ErrorCode global_error_code;
Then a function returning a value, say a struct Foo, and an error/status code could look like;
// The caller needs to check global_error_code for being non-zero, and handle exceptions.
extern "C" struct Foo GetFoo();
The C code call would look like:
struct Foo foo = GetFoo();
if !global_errror_code) {
handleException(); // Or possibly goto :handle_error.
}
Rune code trying to be compatible with Google C++, extern functions should only throw StatusCode error types. The generated C++ headers could wrap Rune functions to return StatusOr<T>. For code using C++ catch-throw, the generated interfaces should wrap the Rune function to convert the error codes to C++ classes or structs, and throw them.
There's a lot more that would need to be done to interface well with C++. For example, we should auto-generate wrapper C++ classes for Rune non-ref-counted classes, where constructors call Rune constructors to get the usual u32 object reference, and store it as a data member in the wrapper. All the public methods could be wrapped using the Rune object reference.
Similarly, we can generate a function API for C, and Rust struct wrappers.
Bill