DEV Community


Posted on

First class citizen

In the past few months I find myself writing a Windows based service using the Go.

Long time ago (almost 20 years back) I used to be a Delphi and VB(A) developer, but I moved to Linux, and almost never looked back again.

I had one small project back in 2012 for a simple cross platform service (it needed to work both on Windows and Linux) and then I learned Go and created it by providing a single binary that "just works".

These months it's something different. I create an infrastructure for a big company that is going to work on over 40,000 machines just on Windows as a standalone servic, and it is an infrastructure for a big high demand service that the company provides to everyone and not just to their regular customers.

Go and Windows

The first thing you notice using Go is that it feels right at home when using it on Unix based systems (Linux in my case).

But Windows is not Linux or Unix for that matter. It does have some POSIX support but it's not a POSIX OS.

I tried to take a string holding environment variable and replace it with it's content, so I found os.Expand that exists for Windows as well, Great.
But when it execute in Windows it does not do a it's job at Windows.

Note that the link is for Windows related functions, but the implementation when you read the code is not for environment variables of Windows that uses percentages as variable marks, but rather for a dollar sign that is used mostly by Unix based shell(s).

Windows has it's own function to do the same action named ExpandEnvrionmentStringsW (or ends with S as well).

I would expected with Go's os package to create env_unix.go and env_windows.go to provide the same function, but internally do different work when having the Go method for doing things.

Citizen Go

I know what you are now thinking, Go is doing a bad work on providing a standard and extended libraries.

Well, sort of, but not really. Only incomplete work for that.

There is a thinking that was not invented by Go - "I" should provide the same syntax regardless of uniqueness of things unless there is absolutely no way in doing that, and then there is an exception for that rule. That is create something with the least common denominator.

Go implement that idea but not invented it. But it did something different then most languages. That is create their own "lingo" for the standard library, rather then using the lingo of a specific environment/operating system.

There are other languages such as Object Pascal and C#* that tries to provide the same standard library idea - emphasis on specific environment regarding the main libraries syntax it provides.

Both Object Pascal (Delphi/FPC) and C# provides Windows based API thinking for everything.

Both Object Pascal and C# run also on Unix based systems today, but still, the API is Windows based, rather then more generic way. It is important to note that C# does much better work then Object Pascal, due to CLI.

Go did the opposite - POSIX is the default (and most common denominator of OS based) rather then Windows, but also provide it's own syntax or "lingo" for that.

But it's not the only way of doing things.

A coin have infinite sides

All three languages: Go, C# and Object Pascal took a shortcut. Both Object Pascal and C# started with Windows, so that was the thinking when creating the language support.

Go started by a Unix Developers (Ken Thompson and Rob Pike), and most usage of Go is on Unix based systems mainly Linux.

But the there are other ways such as how Rust* made it.
Rust provides an OS specific implementation for everything that is not unified.

It makes few things a bit harder to do though (the support for generics didn't help there btw).

For example Unix provides a mask for a file or directory/library - The permission. So a mask for owner to read, write and execute (enter) a directory while group and others can only read and execute (enter) is 0644.

It does not exists for specific file systems, and by default Windows (unless it supports a file system that does support it) does not support it as well.

Using the Rust way will have me check both OS and file systems together.
For example both Linux, Unix and raw have metadata for their own uniqueness, but based on documentation, Windows does not have it.

So at the end developers will create a higher level class/function that tries to answer that need, or will create two implementations:

  1. With masks.
  2. Without masks.

And if you do it for compile time, and for example Windows 10 added Ext4 file system for example, you will not be able to create or change mask.
If it's on runtime, you will not have any support for it.

Let's C

C also has it's own way of doing things, but it's Unix based because C was invented for creating Unix back in the 70's.

The C standard library is actually a POSIX standard, and as such it is very Unix based that every implementation must support a syntax that might not be native to that operating system, and might require additional libraries that will help in that matter.

The best way is...

I do not really know what is the best way for solving this issue.

I find myself less opinionated. I do not think that there is a single way of doing it "right", but only the least worst.

The effort and the result must matter in the path.

  • Important note: I never wrote programs on C# or Rust, only investigated it.

Discussion (0)