... with the least friction?
Incremental adoption was in my mind one of Nim's strong points, but I don't actually know what that workflow looks like. Ignoring a build system at the moment, below is one approach. I'd love to hear more ideas. Incremental adoption is where the intended design of theoretical languages like Carbon and CPPFront may eventually be great, but I wonder if Nim can already do this well and I'm just being obtuse not seeing it.
This example begins wrapping the entry point C source. It also demonstrated overriding a void say() in the C source with a Nim version, and wraps int main() to replace in the future with a full Nim version, passing CLI args and calling it for now. I imagine a similar nim module for each C file, but I'm unsure the C includes and Nim includes will work like I want... I'll have to try it later. But this is enough to ask my general question.
import strutils
proc say() {.exportc.}
{.emit:"""
/*VARSECTION*/ // inject as far down in the header section as possible
""" & staticRead("main.c").
multiReplace(@[
("int main","int cmain"),
("void say","void csay")])
# (override more here as the project progresses...)
# yes, textual replacement is kind of gross and possibly
# results in conflicting symbols. Is there a better way?
.}
proc say() {.exportc.} =
echo "nim version of say() called by main() in original C source"
proc cmain(argc: int, argv: cstringArray):int {.importc: "cmain", nodecl.}
import os
proc main =
echo "nim main(), gathering parameters and forwarding to original C main()"
var argv = allocCStringArray([])
argv[0] = getAppFilename()
for i in 1..paramCount():
argv[i] = paramStr(i)
# call the original c source that we haven't replaced yet
discard cmain(paramCount()+1,argv)
when isMainModule:
main()
Why keep main in C? That's where you should start the "incremental" rewrite. Have the main in Nim instead and call the C code.
But it also depends on the project. Many projects esp if written in C are only big because they are written in C. The C code is an endless repetition of malloc wrappers and manual memory management and manual error propagation, it doesn't do much. You then don't have to "incrementally" rewrite it, just recreate your project in Nim with native Nim libraries.
@Araq thanks for the response and reasoning. I think what you suggest may hold true for C projects, but what about C++? I admit I should have made my demonstration in C++ because that's actually the situation I'm more curious about, but I made C more out of habit for simplicity.
Yes you're right, I would imagine main() would be the first candidate for Nim conversion. The demo is just to show a minimal step to begin that process, where Nim is now the toolchain and conversion can happen as the authors see fit or have resources to do so.
The situation I've been in multiple times and hear some companies talk about is when we have a large modern-ish C++ source code and cannot allocated the 2-9 months it would take to rewrite everything from scratch all at once, especially as the original code is evolving. More attractive is to rework the compilation toolchain and automated build steps in an hour, then tell everyone in the different teams that they need to use Nim now, and not to worry because under the hood it's still the original code. We then replace functions and other pieces as new features and maintenance is required, thereby slowly converting everything to pure Nim. One big benefit of this is that it becomes easier to A/B test unconverted/converted code (including main() itself!).
Thoughts?
Nim can certainly handle incremental conversion of both C/C++ codebases. As @araq says if it's possible to rewrite, it can often be smaller and easier to do so. However, as you mention it's not always feasible.
There's two main routes to take here:
First route: treat your current C/C++ as a library which gets compiled into Nim. You loose your current main, but ideally that should be easy to replace. Then you just move per-function and re-make things like main purely in Nim. This probably fits best into your incremental rewrite approach. Also, you can use c2nim to translate your current main to Nim which is a good first step.
Second route: create libraries from Nim that you export to the large C/C++ project. Effectively just declare some exportc procs that make C/C++ interop easy (checkout ginny for this too). This is best for large projects where you'd want to add Nim into it but where it's not likely for the code base to swap over anytime soon.