This post still has me thinking. Rust's feature set is enormous. The capabilities of the language are insanely powerful. From higher-level iterators to lower-level explicit heap allocations and raw pointers.
Even after it's been rebranded from a "a systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety" (as confirmed the old website) to "a language empowering everyone to build reliable and efficient software", it ultimately remains an SPL. Which is where its power lies, and which generates most of the confusion for most of the newcomers (especially newcomers not only to the language itself, but to the concept and the toolkit of a systems programmer, as such).
Thus, a question - for those of you with enough experience and (hopefully) not enough exposure to the Curse of Knowledge. If you had to guide someone from the beginning - a very inexperienced someone, perhaps not just to Rust, but to programming in general, who's never heard of Assembler before and who has no clue what stack and heap might possibly represent, never mind the "virtual memory", "v-table"-s and such, who had some experience writing some basic code in JS, never bothering venturing down into the rabbit hole of lower-level Node and V8 - how would you do it?
Let's assume our goal would be to turn a total newbie into a rock-star, capable of implementing his own Tokio* library from scratch. Where do we begin, how do we proceed and what do we end with?
From what I've had a chance to gather so far, the lowest possible level from which it makes sense to start thinking about programming is, indeed, Assembler. The language of the CPU - different versions of which are written for different processors' architectures.
Above it you have the languages that compile directly into it - such as C. Since it's basically the first programming tool that sits on top of it, it makes sense that it's through C in particular that most of devices' are programmed - and it's through C that most of their drivers are created - which is a fancy name for programs that are capable of telling these devices what to do.
On top of the hardware, along with its drivers, sits the OS. It coordinates all the metal and silicon beneath it, providing the virtual memory for the programs that will be running on top of it, managing the allocation and de-allocation of resources through its kernel, making sure everything runs smoothly and occasionally slapping its users across the face with a blue screen of death (Windows) or a kernel panic (Linux), especially when (only when) it gets too confused and doesn't know what to do next.
Does our rock-star have to be familiar with kernel programming? If yes, where should they go to learn more about it? Books, videos, online learning resources?
Around the kernel you have the entirety of OS's infrastructure - most of which provides its own set of API's and ABI's (the difference between which I still haven't wrapped my head around myself). If you want to interact with OS, you're not likely to interact directly with the kernel (is that even possible?) - you'll be interacting with its own set of libraries and SDKs instead.
For instance, if you want to create native Windows applications, you'll have to tap into Win32 API - which will give you all the functions, classes and interfaces to create your shiny Windows frames. Should you want to tap into OS's own 'event loop' (I might be going very off course here, so correct me if I'm wrong), such as the one provided by Node, you'll have to tap into different sets of API to make sure your new Tokio* works as expected.
This might be one of the biggest bottlenecks for intermediate programmers, looking to expand their skills. Going through Rust's book is fine and dandy, going through the documentation of Windows API in Hungarian notation (especially when you've never encountered anything remotely as beautiful before) is ... just a tiny bit surprising. What would be your advice here? Which OS parts are the most essential to learn? What can be expected from all OS's? What's most crucial?
don't look at this
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
// Register the window class.
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// Create the window.
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (hwnd == NULL)
{
return 0;
}
ShowWindow(hwnd, nCmdShow);
// Run the message loop.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
EndPaint(hwnd, &ps);
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
On top of the infrastructure of OS you've got all of your compilers, linkers and generally beautifully complex and totally inaccessible to most of the people who've never dealt with anything like it toolkits. LLVMs, GCCs, Rust compiler's set. Most of the programmers will rightly never have to concern themselves with anything here - unless you want to link an icon to an application you're compiling with Rust under Windows, in which case it's either "best of luck" or "forget about it" if you're all on your own.
Being slightly tongue-in-cheek here, but it's definitely one of those parts I wish I knew "The Book" for. All the toolkits mentioned above seem to have a lot in common, yet it's not definitely obvious what one should mostly concern himself with for an all-around, yet profound, expertise.
Now we're getting somewhere. At this level we can finally write some usual Rust code. Alas, as soon as you dive a bit deeper behind the usual: fn main() { println!("Hello, World!"); }
you get to interact with all the power that a language like Rust has to offer. Pointers, references, mutability, concurrency, parallelism, opportunities for asynchronous execution, dynamic allocation, compile-time versus run-time, the "v-table"-s mentioned at the beginning, trade-offs between your arguments being an impl Trait
vs a Box<dyn Trait>
, smart pointers and the usual stack / heap management.
It seems that with all the previous levels sufficiently covered, there should be no questions whatsoever here, aside from Rust's specific features such as ownership and lifetimes. Would you say that is indeed the case? If that's true, what's the most straightforward path to cover all the levels, previously outlined, sufficiently enough? If not, what is the new material that is likely to be introduced here and what would you the best way, in your opinion, to go through it?
Finally, provided that we've turned our rock-star-to-be into almost-a-rock-star by squeezing all of the most relevant info from the previously mentioned levels into their heads, the last barrier would most likely be related to the git mastery, along with packaging, deployment and CI/CD. No code is likely to be written in a vacuum by one person for their and their only sake. Software is written by people, for people - and although it doesn't take long to wrap your head around the git commands and packages, deployment and C(I/D) is far from straightforward at times. But that's a topic for another time.
Would that kind of learning path make any sense for our aspiring Tokio* rock-star developer? Which resources would you recommend to tackle each individual level?
Writing this and having a couple of other tabs with C and C++ tutorials opened, I realize that people coming from them / starting with them inadvertently get a head start, as they force you to learn about pointers, memory management and all the troubles that come with them - whether you like it or not. Rust is much more protective and thus much less revealing by nature - which is great if you only want to use existing libraries, written by people who actually understand what's going on, but not that great for everyone else, who might not necessarily know where to even start exploring these topics.
Thus, this question.
*Tokio was chosen because no matter how much I tried to at least scratch the surface of asynchronous execution, the best explanations I could find were written for Python and JS, using generators, which are still experimental in Rust. If anyone has any resources explaining on the lowest level possible how async actually works, from scratch - starting from a purely imperative style of Assembler's instructions, which is ultimately what any CPU on any machine actually executes, please enlighten me.