A look at Go through the eyes of a .NET developer. Week # 1


    My name is Lex and I am the host of the YouTube channel " IT Beard ". And I’m a 6-year-old affiliate. Recently, I had a desire to go beyond my core technology (C # /. NET), and to understand the essence of the Blob paradox . I firmly decided that I would try myself in another language, and the choice by chance fell on Go.

    In order to get knowledge in a structured way, I signed up for a course from mail.ru, which you can find here: https://www.coursera.org/learn/golang-webservices-1 . Actually, what I learned from the first week of training in this course will be discussed further. Let's go!

    I'll start with a small intro on the language. Go was already developed at the time of multi-core processors (2007-2009), so everything is very good here with parallelizing work across the cores. In addition, the language works great with competitive (simultaneous) requests. In general, a godsend for all kinds of web services and loaded web systems. Just what is needed for me, because I have been developing web api (Web API) in recent years.


    To work with the language, it is enough to install the Go tools software package of 118mb in size, and you can start coding.

    The scripts have the * .go extension and are run by the go command on the command line (I am an adherent of windows command line). The gofmt command places all indentation-spaces and makes candy from the file (code beautifier out of the box). As in my favorite subnet, in Go, program execution begins with the main method .
    A cool feature immediately caught my eye - you can not put a semicolon at the end of the line :)

    Go is another language (along with python, c #, php, js, ts and others), which is convenient to work with in VSCode. VscodeIt delivers absolutely all the necessary packages and dependencies as the code is written. When saving the IDE, kindly runs gofmt for you and makes the code even more beautiful.


    Everything is familiar here: there are many different types, there are default values. From the unusual for the donor, the var keyword means that the variable will go further, and the type of the variable can be declared or not already after the variable name. Unusually.

    In general, there are many ways to declare and initialize variables. You can even assign values ​​to multiple variables, separated by commas, on the same line. And by the way, there are no implicit casts.

    And Go also has a " : = " operator , which allows you to declare and immediately initialize new (and almost always only new) variables. Hi Pascal;)

    Record of the form:

    perem1, perem2 := 2, 3
    either creates two new variables, or creates one of them, and the second simply assigns a new value. This is to say that if both variables already exist, then this will not work and you will get an error, they say use the usual assignment. Operator : = must definitely create something :)

    And there is also a funny fake - underscore symbol "_". It means the absence of a variable, something like that :) (in the sharps, as it turned out, it also is: https://docs.microsoft.com/en-us/dotnet/csharp/discards ) A


    funny moment with the lines: the standard length calculation line strings len (str) counts bytes , and in UTF8 a character can weigh more. To count characters called runes, you need to use the utf8.RuneCountInString (str) method from the "utf8" package. And, and yet - we do not change the lines (just like in your favorite one).


    Constants can be declared in batches. Constants have an interesting type, or something - iota . I will call it Jopta. Jopta allows you to do some constants on the basis of others, something like an iterator for constants (something reminiscent of yield from a subnet).

    And yet, constants may not be typed. For such constants, the compiler decides what type they are at the right time. Conveniently, what is already there.


    Yes Yes! There are pointers here. But it’s not very scary. They say that they are not numbered, and you can’t add the fourth pointer to the pointer to get the next pointer. Here it is kind of like a variable that stores a link to another variable (so far I somehow understood it).

    The pointer is specified through an ampersant or the new (type) method (to get a pointer to a type, not a variable):

    If you access the pointer directly, the link changes. And if through the operator " * ", then the value of the variable that lies behind the pointer (to which it points) changes. In general, it is not very clear yet, but then, they say, it will be clearer.


    Array size is part of the data type! So, arrays of different dimensions in fact are different types of data and are not compatible.

    But what for arrays are needed if they are so dry and immutable (they cannot be changed in runtime). Therefore, we came up with such slices (slices) based on arrays.

    We have a slice length (length) and capacity (capacity) . It reminded me a bit of the nvarchar data type from SQL. Those. you can allocate memory space (memory allocation) for your arrays, but put arrays of any length there (up to capacity). In short, the thing is interesting, you need to get used to it. For convenient use of slices, there are all sorts of methods such as make (), append (), len (), cap ()and probably others. From one slice (slice), you can slightly obtain a sub-slice (slice slice). And by the way, append () extends the slice capacity.

    If the sub-slice is equal to the slice, then in fact they will refer to one piece of memory. And accordingly, a change in the value in one slice will lead to a change in the second slice. But if one of them is expanded through append () , a new piece of memory will be allocated.
    In short, everything is serious with memory :)

    Map, hash table, associative array

    All of this is one and the same. Go has hash tables for quick key searches. Initialized as follows:

    You can do sub-tables (Map Map), etc. (any level of nesting, sort of like). There is a default value that is returned on non-existent keys. It is taken from the key type (for bool, then false). To know if the default variable has returned, i.e. that the key is missing - use the key to the existence of the key (interesting thing):

    Control structures

    There is an if-else. Slightly more interesting than in sharps, because you can use the initialization conditions for if.

    There is a swith-case. Breaks do not need to be set, and this is a difference. Inversion of logic) If you want the following condition to be checked too, you need to write fallthrough .

    There is for. And all with cycles.

    With iterating over slices, it’s more fun (range operator):

    And even with a hash map you can iterate (although the sequence will often be different, because the map is not directional):

    For type string range iterates over runes, not bytes.


    Declared via the func keyword . Moreover, the inversion of logic is again compared to the subnet - the input parameters are first indicated, and then the type of the return value (you can immediately named it).

    Functions can return multiple results , just like tuples in a subnet .

    The named values ​​that the function returns are initialized by default with default values ​​for the type.

    And yet, you can make variable functions that have an unlimited number of input parameters of the same type (like param in the subnet). And then at the input of the function comes a slice of a specific type.

    Functions may have names, or may be without a name - anonymous. And they can be called, can be assigned to variables. It looks like JavaScript. You can even make types based on functions! Functions can be passed as parameters (read delegates from subnet).

    There is even a closure (uuuh, scary)! You can reach out variables from a function from it (read, in the parent function). These are the pies.

    You can declare deferred execution of functions . Through the defer keyword this is done. Those. deferred functions are executed at the end of the function in which they are declared, in the reverse order of the declaration of these deferred functions. Here. Moreover, the initialization of the arguments of deferred functions occurs when the defer block is declared. This is important, especially if another function is taken as an argument - it will be executed much earlier than you expect!

    PANIC! Go has such a function - panic () . It’s like throw in, but worse. This function stops program execution. But this same panic can be handled by defer, as it is executed in any way at the end of the function, even after a panic. And yet - panic, this is not a try-catch. This is worse!

    Structures (near-objects)

    Go is said to be not quite an OOP-paradigm language. But there are things like structures . If you are from the world of subnetting, then you know that we also have structures - these are mappings of objects that can be stored on the stack (value types). In Go, structure is kind of the only way to do something like an object. I can’t talk about the types of value yet, but the nature of the interaction with structures is very similar to the detailed one.

    Each structure is essentially a separate type and can have a set of properties of specific types (including the type of another structure or type of function):

    In addition, a certain mechanism of direct object inheritance from donnet is seen here. In the picture above, the Account structure is nested with the Person structure. This means that all fields from Person will be available in a variable of type Account . If the property names coincide (in the example above Id, Name ), there will be no conflict, but the value of a higher field will be taken:


    Yes, there is not only the similarity of objects, but also methods. Almost :) OOP method is a function , tied to a particular type (e.g., structure). A method differs from a function in a declaration: after the func keyword, in brackets you need to specify the type this method belongs to and the role of passing a variable of this type. What is the role? If you put an asterisk in front of the type, then the variable will be passed by reference (remember the pointers, it was there too), and as a result, inside the method we will work with a specific type, and not a copy of it.

    From the picture above, we can conclude that UpdateName does not make sense, because it changes the copy of the structure, and not the original. And this copy does not return. While SetNamewill change the original structure (thanks to the asterisk and the link).

    Methods in structures are inherited (and according to the rules of property inheritance), i.e. the parent structure has access to all methods nested in its structures.

    Other types can have methods, not just structures. This may seem like extension methods from subnet, but no. Methods in Go can only be created for local types, i.e. types that are declared in this package (about packages a little further).

    Packages, Scope, Namespaces

    Only now I realized that Go does not have NEIMSPACE! At all! I realized this when, after compilation, an error came out, they say in one folder I have two files with the main method. It turns out that at the compilation stage everything from daddy seems to be glued together in one canvas! A folder in fact is a namespace. Here is such magic, comrades :)

    Here, by the way, what is said in the dock:

    Note for yourself: smoke a dock
    And here's another little article in the topic: https://www.callicoder.com/golang-packages/#the-main-package- and-main-function

    This is what they say, the basic folder structure looks like:

    bin - compiled
    pkg binaries - temporary
    src object files - sources
    Packages refer to the name of the folder in which the package files are located. Yes, a package can have many files that are imported into each other.

    Another interesting rule: functions, properties, variables and constants starting with a capital letter can be used outside the package , i.e. be imported . Everything with a small letter is used only inside the package. An analogue of access modifiers from subnet.

    The import keyword works within a file, not a package.


    The interfaces here are interesting. We do not need to inherit interfaces from our structures. It is enough that the structure, which comes as an input parameter to a certain method that takes an interface type, implements the methods of this interface. And no problem. Those. interface methods need not be necessarily implemented by the structure. But the structure should contain the implementation of all the methods that are required in a particular function, where the interface type is used as an input parameter.

    It turns out that, in contrast to, in Go, an interface is a characteristic of the function in which it is used, and not a characteristic of a specific structure (object).

    If it is even simpler, then the logic is this: you do not need to be able to quack to be a duck. If you know how to quack, then most likely you are a duck :)
    And by the way, if your structure implements all the methods of some interface, then this structure can be assigned as the values ​​of the variable of the implemented interface . Something like this :)

    Type switch case

    Go has a special switch-case that can work depending on the incoming type. A cool thing:

    By the way, you can convert one type to another (for example, an interface type into a structure type to get structure fields that are not accessible from the interface):

    ok - a Boolean sign that the conversion was successful. We have already encountered the signs :) The

    empty interface is a beast in the world of Go. It can take any type at all. Something like dynamic or Object in. For example, it is used in fmt.Println () and similar functions that use an empty interface in the implementation.

    Interfaces can be built into each other, and thus make the composition of the interfaces (what is said in the letter I (interface segregation) of SOLID principles )


    In Go, all test functions begin with the word Test , and the input of the testing module testing is accepted . Files are named by the name of the test file + word _test ( main_test.go - tests for the main.go file ). Moreover, both tests and test files are in one package!


    That's all! Thank you for your attention, and I am ready to discuss issues in the comments. I'll meet you in the following notes from my classes!

    PS You can look at all my code walks on this week of training on github

    Also popular now: