Speaker 1: Welcome to the deep dive. Today. We're really getting into
Speaker 1: the fundamentals of the Go programming language. We're using Go
Speaker 1: fundamentals Go for guides as our source here, and our
Speaker 1: mission really is to pull out the core ideas how
Speaker 1: GOT structures projects, it's basic elements, how it handles dependencies,
Speaker 1: basically giving you a solid understanding without feeling like you're
Speaker 1: reading a textbook exactly.
Speaker 2: We'll be looking at modules, packages, dependencies obviously, but also
Speaker 2: the language basics, numbers, strings.
Speaker 1: Booleans, variables, constants.
Speaker 2: Variables, constants, how to format things, and then collections like
Speaker 2: arrays and slices, maps, control structures, functions, interfaces.
Speaker 1: Error handling too, right, yeah, and concurrency it's a big one.
Speaker 2: And go oh yeah, definitely, guroteines, channels, context synchronization, primitives,
Speaker 2: even touching on the filesystem stuff. It's quite a bit,
Speaker 2: but we'll break it down.
Speaker 1: That's good. So for you listening, maybe you're prepping for
Speaker 1: a tech chat, or just want to brush up on Go,
Speaker 1: or you're just curious, this deep dive should give you
Speaker 1: a structured, insightful look. Hopefully some moha moments along the way,
Speaker 1: let's hope. So okay, let's start unpacking it at the
Speaker 1: very foundation. How does Go organized code.
Speaker 2: Right, It really starts with modules. Think of the Go
Speaker 2: dot mod file. It's goes way of promising reproducible builds.
Speaker 1: Reproducible builds meaning naing.
Speaker 2: Everyone on the team or you know, your build server
Speaker 2: uses the exact same versions of all the libraries. The
Speaker 2: Go dot mod file names your project, says what Go
Speaker 2: version it needs. Like the examples we looked at use
Speaker 2: Go one point nineteen.
Speaker 1: Okay, so godat mod is the project manifest and inside
Speaker 1: modules we have packages exactly.
Speaker 2: Packages are the fundamental unit of importable code. The key
Speaker 2: thing with naming them. Keep it short, simple, easy to recognize.
Speaker 1: Like squile L or HDPP precisely.
Speaker 2: Much better than say structured query language. Imagine typing that
Speaker 2: out every time you use it.
Speaker 1: Yeah, that would get old fast. Yeah. What if you
Speaker 1: have two packages with the same name, like from different places.
Speaker 2: Ah, good question. That's where import aliasing comes in. If
Speaker 2: you import say demo fubar and maybe another place bar,
Speaker 2: you can give one an alias like import pub demo fubar.
Speaker 1: Oh I see, so you'd use pub dot function name
Speaker 1: for one and maybe just bar another function for the
Speaker 1: other exactly.
Speaker 2: It avoids those name clashes and keeps things clear.
Speaker 1: Okay, so go dot mod tracks external stuff packages organize
Speaker 1: your internal code. How do you actually get those external libraries?
Speaker 2: That's the go get command. So if you need let's
Speaker 2: say that fleck library for working with words, Yeah, you
Speaker 2: just run Go get GitHub dot Com forward Slash, go Buffalo, flecked,
Speaker 2: Go fetches. It updates your Go dot mod with the dependency.
Speaker 1: In the version, and it also creates that go dot
Speaker 1: sum file. Right. What's that for again?
Speaker 2: Right, the go dot sum It's basically a lockfile with
Speaker 2: cryptographic hashes like digital fingerprints for your dependencies. It ensures
Speaker 2: the code you downloaded is exactly what it's supposed to be,
Speaker 2: hasn't been tampered with. Consistency and security, got.
Speaker 1: It like a security checkpoint and versions. How does go
Speaker 1: handle updates?
Speaker 2: It uses semantic versioning, you know, the major dot minor
Speaker 2: dot patch system.
Speaker 1: Where major means breaking changes, minor means new features, Patch
Speaker 1: means fixes pretty much.
Speaker 2: Yeah, Go get usually updates you to the latest minor
Speaker 2: or patch versions safely. One thing I'll watch out for, though,
Speaker 2: is sychnical imports.
Speaker 1: Where package A needs b and B needs.
Speaker 2: A Yeah, that can get messy fast, generally best to
Speaker 2: avoid that.
Speaker 1: Structure makes sense, Okay, modules, packages, dependencies clear. Now let's
Speaker 1: dive into the language itself, the basics.
Speaker 2: Okay, So GO is statically typed.
Speaker 1: Meaning it checks types before you run the code.
Speaker 2: Exactly, catches lots of errors early, like trying to add
Speaker 2: text to a number. It's also compiled straight to machine code,
Speaker 2: so it's fast, and it has garbage collection.
Speaker 1: The automatic memory cleanup.
Speaker 2: Crew that's the one takes a lot of manual memory
Speaker 2: management burden off the developer.
Speaker 1: Big plus definitely sounds helpful. Yeah, okay, let's talk numbers.
Speaker 1: What are the options?
Speaker 2: GO gives you a fair range. You've got integers signed
Speaker 2: in tate through in sixty four and unsigned and dad
Speaker 2: on sixty four plus t us the general int and
Speaker 2: that depend on your system architecture, and for decimals float
Speaker 2: thirty two and float sixty four. Usually float sixty four
Speaker 2: is the default choice unless you have specific needs.
Speaker 1: That's a lot of integer types. How do you choose?
Speaker 2: Often you can just start with int for whole numbers
Speaker 2: and float sixty four for decimals. The specific sizes like
Speaker 2: in eight become important when you're really optimizing for memory
Speaker 2: or you know, dealing with specific hardware interfaces.
Speaker 1: Okay, what about putting two big a number into a
Speaker 1: small type overflow right overflow?
Speaker 2: The compiler might catch it if you try to assign
Speaker 2: a value that's obviously too big, like varx once equals
Speaker 2: three hundred, that won't compile.
Speaker 1: But what about during calculations.
Speaker 2: Ah, that's different. If you add say one to a
Speaker 2: one eight that's already two fifty five, it doesn't crash.
Speaker 1: It wraps around, wraps around two zero yep.
Speaker 2: Wraps around to zero. Go doesn't automatically saturate or stop
Speaker 2: the calculation by default.
Speaker 1: Something to be aware of, good to know, subtle. Okay,
Speaker 1: what about text strings In GO?
Speaker 2: Strings are pretty straight forward. You've got your standard double
Speaker 2: quoted strings. They interpret escape sequences like in for new.
Speaker 1: Line t for tabs, standard stuff.
Speaker 2: And then you have raw string literals using backticks inside
Speaker 2: these backslashes or just backslashes, no special meaning.
Speaker 1: That sounds useful.
Speaker 2: It's great for multiline strings, like embedding JSON or writing
Speaker 2: regular expressions where you'd otherwise be escaping constantly. And GO
Speaker 2: handles UTF eight out of the box, so complex characters
Speaker 2: different languages.
Speaker 1: Usually just works and what's a roun?
Speaker 2: A roune is basically an alias for N thirty two.
Speaker 2: It represents a single Unicode code point, which might be
Speaker 2: one or more bites in UTF E.
Speaker 1: Okay, so strings cover text? Well? Yeah, what about simple
Speaker 1: true or false?
Speaker 2: That's the bool type values are just true or false
Speaker 2: essential for conditions obviously.
Speaker 1: Right, numbers, text booleans. Yeah, how do we actually hold
Speaker 1: onto these values? Variable?
Speaker 2: Yep, Variables You declare them with var than the name
Speaker 2: than the type like var count int.
Speaker 1: You assign with what If you declare but don't a sign,
Speaker 1: then it.
Speaker 2: Gets the zero value for its type zero for numbers,
Speaker 2: empty strength for strings, false for bulls, and nil for pointers, slices, maps, interfaces,
Speaker 2: et cetera.
Speaker 1: Mil Okay, is there a shorter way to declare? Yes?
Speaker 2: The short variable declaration name value. This declares and initializes
Speaker 2: in one go and go infers the type from the value.
Speaker 1: Very common, ah, the amperator got it?
Speaker 2: And naming rules start with a letter or underscore can
Speaker 2: contain letters, numbers, underscores, case sensitive, can't be a keyword
Speaker 2: like funk or if and go style prefers short names
Speaker 2: often yes, especially for variables with a very limited scope
Speaker 2: like loop counters Terseness is common, but clarity still matters,
Speaker 2: of course makes sense.
Speaker 1: And constants, how are they different?
Speaker 2: Declared with const The key thing is their value cannot
Speaker 2: be changed after declaration. They're fixed. They can be typed
Speaker 2: or untyped. Untyped constants are a bit more flexible.
Speaker 1: Okay, var for changeable const for fixed. Now, how do
Speaker 1: we display these values? Formatting?
Speaker 2: That's where the FMT package comes in. It's your standard
Speaker 2: in output.
Speaker 1: Toolkit FMT dot pron FMT dot print of those ones.
Speaker 2: Exactly, printlin prints things with spaces in between, and adds
Speaker 2: a newline. Print for formatted output using.
Speaker 1: Verbs verbs like percent or percent right.
Speaker 2: Percent is the default value. Format prients two shows the
Speaker 2: type percent for decimal integers, percents for strings, percent reported strings,
Speaker 2: percent for floats, lots of options sprintoff works like print,
Speaker 2: but returns the formatted string instead of printing.
Speaker 1: It useful and you can control padding and stuff.
Speaker 2: Yep padding integers, printing signs showing base prefixes for binary pers,
Speaker 2: octal perso x, percentx escape sequences like N and T
Speaker 2: work in strings too. FMT is pretty powerful for basic formatting.
Speaker 1: Cool. Okay, basic types and printing cover. Let's move on
Speaker 1: to collections, arrays, and slices.
Speaker 2: Right, So, rais and GO have a fixed size. You
Speaker 2: declare an array, say varnums five int, and it always
Speaker 2: has five integers. Can't change that size later.
Speaker 1: And all elements must be the same time correct.
Speaker 2: If you don't initialize it, elements get the zero value.
Speaker 2: The compiler also helps prevent you from accessing an index
Speaker 2: that's out of bounds. Importantly, arrays are value types value.
Speaker 1: Types, meaning if you assign one array to another, it
Speaker 1: copies everything exactly.
Speaker 2: You get a full independent copy.
Speaker 1: Okay, so fixed size value types. That sounds limiting sometimes,
Speaker 1: what about slices.
Speaker 2: That's where slices come in. They are dynamically sized flexible
Speaker 2: views into an underlying array, much more common in GO
Speaker 2: code than raw arrays. The view into an array, yeah,
Speaker 2: a slice has a pointer to the underlying array, a
Speaker 2: length how many elements it currently holds, and a capacity
Speaker 2: how large the underlying array segment is. You can create
Speaker 2: them with it literal like in one, two, three, or
Speaker 2: using the make function. Make yent five creates a slice
Speaker 2: of five integers initialized to zero. Make zero ten creates
Speaker 2: an empty slice, but with an underlying array capacity of
Speaker 2: ten ready to.
Speaker 1: Grow, and slices are reference types unlike arrays.
Speaker 2: Correct assigning a slice just copies the slice header pointer, length, capacity,
Speaker 2: not the underlying beta. Both slices will point to the
Speaker 2: same data.
Speaker 1: Ah, so changes through one slice are visible through the other.
Speaker 2: If they point to the same underlying elements.
Speaker 1: Yes, how do you add elements to a slice? It's
Speaker 1: it's their dynamic.
Speaker 2: You use the built in a pen function. My slice,
Speaker 2: a pend, my slice new value. If there's enough capacity
Speaker 2: in the underlying array, it just adds the element and
Speaker 2: increases the length.
Speaker 1: And if there isn't enough capacity.
Speaker 2: Then a pen allocates a new, larger underlying array, copies
Speaker 2: the old data over adds the new element, and returns
Speaker 2: a slice pointing to this new array. That's why you
Speaker 2: always assign the result of a pend back to your
Speaker 2: slice variable.
Speaker 1: Got it? You can depend a whole other slice too, right, yep?
Speaker 2: Using the operator a pend slice one, slice two. And
Speaker 2: there's a copy function to explicitly copy elements between slices.
Speaker 1: Okay, fixed arrays flexible slices, how do we loop through them?
Speaker 2: The four loop is goes only looping construct you can
Speaker 2: use the classic C style for with an index for
Speaker 2: i or o omeaning my slice i plus plus slice
Speaker 2: familiar but more idiomatic, and go is using with a
Speaker 2: range keyword for index value range my flesh. It iterates
Speaker 2: over the collection, giving you the index and the value
Speaker 2: at each step.
Speaker 1: Range works on a rais and slices what else.
Speaker 2: Maps strings gives indexed and run and channels only built
Speaker 2: in collection types. Mostly inside loops, you can use continue
Speaker 2: to skip to the next iteration or break to exit
Speaker 2: the loop entirely.
Speaker 1: Standard loop control. Okay, foreign range. Let's talk about maps
Speaker 1: key value pairs.
Speaker 2: Exactly like dictionaries or hash maps and other languages rate
Speaker 2: for lookups, you declare them using make map key type,
Speaker 2: value type, or a map literal like map string at one, one,
Speaker 2: two to two.
Speaker 1: How do you add or get values.
Speaker 2: Using square brackets with the key my map key value
Speaker 2: to set and vowel my map key to.
Speaker 1: Get What if the key isn't there when you try
Speaker 1: to get it.
Speaker 2: You get the zero value for the value type e g.
Speaker 2: Zero for end for string. But there's a better way
Speaker 2: to check if it exists. The calm op idiom value okay,
Speaker 2: my map key here okay will be boolean true if
Speaker 2: the key existed false. Otherwise value holds the actual value
Speaker 2: if okay is true, or the zero value if false.
Speaker 2: Prevents confusion between a stored zero value and a missing key.
Speaker 1: Ah, that's neat. Do maps have a fixed size?
Speaker 2: No, they grow dynamically as you add more entries. Practically
Speaker 2: unlimited really just limited by memory. Like slices.
Speaker 1: There are reference types, and the order you get things
Speaker 1: back when iterating isn't guaranteed correct.
Speaker 2: Iteration order over a map is intentionally randomized and go.
Speaker 2: Don't rely on it got.
Speaker 1: It maps for lookups, no guaranteed order. How about making
Speaker 1: decisions if statements.
Speaker 2: Yip standard to if lcfls. One neat GO feature is
Speaker 2: that you can declare a variable scope just to the
Speaker 2: if statement itself. If value okay, my map key okay,
Speaker 2: value and okay only exist inside the if in any
Speaker 2: else blocks.
Speaker 1: Oh that's tidy, keeps the scope tight. Yeah. What about switch?
Speaker 2: Switch is powerful and go. You can switch on a
Speaker 2: value and then have different case blocks. Unlike see your Java,
Speaker 2: there's no automatic fall through. A matched case executes and
Speaker 2: then the switch is done.
Speaker 1: No need for break everywhere exactly.
Speaker 2: If you want fall through, you have to use the
Speaker 2: fall through keyword explicitly. You can lists multiple values in
Speaker 2: a single case, and there's a default case. You can
Speaker 2: even use switch without an expression, making each case just
Speaker 2: a boolean condition switch case by zero, case by.
Speaker 1: Zero like a cleaner if lsif chain nice. Let's package
Speaker 1: up reusable code functions.
Speaker 2: Right define with the funk keyword. Function function name r
Speaker 2: one type one rchqwo type two return type. The main
Speaker 2: function in the main package is the entry point for
Speaker 2: executable programs.
Speaker 1: You have to declare the type for every argument.
Speaker 2: Yes always funk ad a in b nt in return
Speaker 2: a plus b. You can group types if consecutive ARBs
Speaker 2: have the same type funk ad a b n t in,
Speaker 2: and return values can return zero or more values. Just
Speaker 2: list the return types after the arguments. Funk divide ab
Speaker 2: float sixty four, float sixty four error. Returning an error
Speaker 2: as the last value is the standard go away to
Speaker 2: signal failure.
Speaker 1: Multiple return values are common, then.
Speaker 2: Very common, especially for the value and error pattern. You
Speaker 2: can also name the return values funk split some int
Speaker 2: x ynt. They act like variables declared at the start
Speaker 2: of the function. If a function returns values, the caller
Speaker 2: has to handle or explicitly ignore them using funk.
Speaker 1: Okay, what if a function needs to accept, say any
Speaker 1: number of arguments.
Speaker 2: That's very atic. Arguments use before the type of the
Speaker 2: last parameter funks, numbs, dot nt intinct. Inside the function,
Speaker 2: numbs is treated as a slice event, so you could.
Speaker 1: Call some one to two or some one two.
Speaker 2: Three four exactly. Very flexible for things like logging functions
Speaker 2: or database query builders.
Speaker 1: Cool. Now, what about defer That seems unique to.
Speaker 2: Go It is pretty distinctive. Differ schedules a function call
Speaker 2: to be executed just before the surrounding function.
Speaker 1: Returns, before it returns like at.
Speaker 2: The very end yep whether the function returns normally or panics.
Speaker 2: The deferred calls run. If you have multiple defers, they
Speaker 2: execute in last in first out laf order.
Speaker 1: I have a FILT, so the last one deferred runs first.
Speaker 2: Correct. The most common use is resource cleanup, like open
Speaker 2: a file, then immediately defer file close. Guarantees it gets closed.
Speaker 1: Even if there's an error later in the function.
Speaker 2: Exactly. It simplifies cleanup logic immensely. One key point the
Speaker 2: arguments to the deferred function are evaluated when the defer
Speaker 2: statement is executed, not when the call actually happens.
Speaker 1: Ah. Okay, So if you defer function using a variable,
Speaker 1: it uses the variable's value at that moment.
Speaker 2: Right, Something to keep in mind, especially with loops and
Speaker 2: anonymous functions.
Speaker 1: Got it defer for guaranteed cleanup? What about the innit function?
Speaker 2: A knit is special. It runs automatically when its package
Speaker 2: is initialized before main runs. You don't call it yourself?
Speaker 1: When would you use it?
Speaker 2: Usually for package levels setup you can have multiple init
Speaker 2: functions in a file or package. Within a file, they
Speaker 2: run in order of appearance across files in a package.
Speaker 1: The order isn't guaranteed, so maybe setting up database connections
Speaker 1: or registering.
Speaker 2: Things sometimes use for that. Yeah, like registering image formats
Speaker 2: by importing the image package for its in it side effect.
Speaker 2: But relying on import side effects is often seen as
Speaker 2: an anti pattern. Now, better to be explicit.
Speaker 1: Okay, in it for setup, but use with caution. Let's
Speaker 1: move to structuring more complex data structs and methods.
Speaker 2: Structs let you group different pieces of data together into
Speaker 2: a single custom type type, person, struct, name, string, age.
Speaker 1: And p. Simple enough, how do you access the fields?
Speaker 2: Dot notation dot p person name, Alice age thirty f
Speaker 2: mt printline P name.
Speaker 1: What about embedding.
Speaker 2: AH, Embedding is like anonymous fields type employee struct salary
Speaker 2: float sixty four. Now an employee value E has field
Speaker 2: Z salary, but also E name and ehe promoted from
Speaker 2: the embedded person struct.
Speaker 1: Do you access the embedded fields directly? Cool and methods
Speaker 1: functions attached to types.
Speaker 2: Exactly, You define a method with a receiver funker pee
Speaker 2: person Hell, my name is peak person p person grant
Speaker 2: here pee person is the receiver.
Speaker 1: You can have pointer receivers too, right, yeah, funk person
Speaker 1: setage new agent. Right.
Speaker 2: The big difference pointer receivers person can modify the original struct.
Speaker 2: Value value receivers person operate on a copy of the struct,
Speaker 2: so modifications inside the method don't affect the original.
Speaker 1: So use pointers if you need to change the struct.
Speaker 2: Generally yes, or if the struct is very large and
Speaker 2: you want to avoid copying it for performance reasons. Value
Speaker 2: receivers are simpler if you don't need mutation.
Speaker 1: Can you add methods to any type like built.
Speaker 2: In types, not directly to built ins like INNT, but
Speaker 2: you can define a new type based on it. Type
Speaker 2: my int int and add methods to my ind However,
Speaker 2: the new type doesn't inherit methods from the underlying type.
Speaker 2: You can also define methods for types from other packages,
Speaker 2: but only within the package where you define the method.
Speaker 1: What about calling methods on nil pointers?
Speaker 2: That can cause a panic if you're not careful. Methods
Speaker 2: with pointer receivers should ideally check if the receiver is nil.
Speaker 2: If p equals nil.
Speaker 1: Good practice, Okay, structs and methods. What about pointers themselves?
Speaker 2: Pointers hold the memory address of a value. GO is
Speaker 2: pass by value, meaning function arguments are copies. Pointers let
Speaker 2: you effectively pass by a reference, allowing functions to modify
Speaker 2: the original value.
Speaker 1: How do you work with them?
Speaker 2: Type declares a pointer type for example int, and the
Speaker 2: variable gets the memory address. Creates a pointer pointer variable
Speaker 2: to references the pointer to get the value it points to.
Speaker 1: So she takes the address follows the address pretty much.
Speaker 2: Using pointers avoids copying large data structures, which can be
Speaker 2: good for performance, but it also means you need to
Speaker 2: think about shared access and potential garbage collector impact.
Speaker 1: Got it pointers for indirect access and modification? Now testing
Speaker 1: crucial part of development. How does go do it?
Speaker 2: Go has great built in testing test files, end and
Speaker 2: test dot go test functions start with test and take
Speaker 2: a testing dot T argument test my function testing dot T.
Speaker 1: And it is used for reporting.
Speaker 2: Failures right TFA TALF expected percingop percy and expected actual
Speaker 2: logs and error, but continues t fetalf logs and stops
Speaker 2: the current test immediately. Good error messages are key.
Speaker 1: How do you run test Go tests in.
Speaker 2: The directory go test dash v gives verbose output showing
Speaker 2: each test run.
Speaker 1: What about code coverage? Seeing what percentage of your code
Speaker 1: the tests hit.
Speaker 2: Easy, Go test dash Tierra profile coverage dot out generates
Speaker 2: a profile. Then Go tool cover dash HTML coverage dot
Speaker 2: out opens a nice HTML report showing covered and uncovered lines.
Speaker 2: Very useful.
Speaker 1: That sounds great for finding gaps. Any techniques for organizing tests, Table.
Speaker 2: Driven tests are very popular and effective. You define a
Speaker 2: slice of test cases often strucks, each containing inputs and
Speaker 2: expected outputs. Then one loop runs all the cases.
Speaker 1: Keeps things dry, don't repeat yourself exactly and clear.
Speaker 2: Go also supports subtests using t run subtest name funct
Speaker 2: testing dot T you. This gives better output organization and
Speaker 2: lets you run specific subtests. You can even run subtests
Speaker 2: in parallel with t.
Speaker 1: Parallel tests nice any other useful GO test flags lots.
Speaker 2: Short skips, long running tests if you mark them. Booker
Speaker 2: na sets the number of tests to run in parallel.
Speaker 2: You can run specific tests by name using NUSH run rejects,
Speaker 2: nushtime out duration, NESHO, foul fast, NUSH account one to
Speaker 2: disable caching, plenty.
Speaker 1: Of control, and test helper functions.
Speaker 2: Yeah common practice to extract reusable logic into helper functions
Speaker 2: within the test file. Conventionally, they take testing dot t
Speaker 2: B as the first argument, which works for both tests
Speaker 2: testing dot t and benchmarks testing B.
Speaker 1: Cool solid testing framework. Let's switch gears to interfaces, a
Speaker 1: core concepting GO.
Speaker 2: Interfaces define a set of method signatures. They specify behavior.
Speaker 2: What makes Go different is implicit implementation.
Speaker 1: Implicit meaning you don't have to say my type implements
Speaker 1: my interface exactly.
Speaker 2: If your type has all the methods declared in the
Speaker 2: interface with the right signatures, it automatically implements that interface,
Speaker 2: no explicit declaration needed.
Speaker 1: Interesting like the io writer example.
Speaker 2: Perfect example, io writer requires a right byte int error method.
Speaker 2: Both o's file and bytes buffer have that method, so
Speaker 2: they both satisfy io writer even though they're very different types. Internally,
Speaker 2: you can create your own types that implement interfaces to
Speaker 2: So it's.
Speaker 1: If it walks like a duck and quacts like.
Speaker 2: A duck, it implements the duck interface.
Speaker 1: Basically, Yeah, what are the benefits?
Speaker 2: Huge flexibility, abstraction. You can write functions that accept an
Speaker 2: interface type and they'll work with any concrete type that
Speaker 2: provides the required behavior. Great for decoupling parts of your
Speaker 2: system and for testing. You can easily pass in mock implementations.
Speaker 1: And the advice is to keep interfaces small.
Speaker 2: Yes, very much so. The GO philosophy favors small, focused interfaces,
Speaker 2: often with just one or two methods. Interfaces define only methods,
Speaker 2: not fields.
Speaker 1: Can you use interfaces to make functions more specific about
Speaker 1: what they need?
Speaker 2: Definitely? Instead of insert in data any, maybe insert in model.
Speaker 2: Where a model is an interface requiring an id int method.
Speaker 2: It clarifies the expectation.
Speaker 1: What about the empty interface?
Speaker 2: Any any or interface is the interface with zero methods.
Speaker 2: Since every type has zero or more methods, every type
Speaker 2: implements any so.
Speaker 1: It can hold literally anything.
Speaker 2: Yes, but you lose static type safety. To get the
Speaker 2: underlying concrete value back, you need a type assertion.
Speaker 1: The comic coaduck again vow okay, buy any dot string exactly.
Speaker 2: That's the safe way. If the assertion fails, okay as
Speaker 2: falls no panic. You can also use a type switch
Speaker 2: switch v ANI types to handle multiple possible types safely okay.
Speaker 1: Any for ultimate flexibility, but use assertions carefully. Now, errors
Speaker 1: and panics. How does go handle things going wrong?
Speaker 2: The standard way is using the built in air interface.
Speaker 2: It just requires an error string method. Functions that can
Speaker 2: fail return an error as their last return value. If
Speaker 2: everything's okay, they return nil.
Speaker 1: For the error, So check if air nil.
Speaker 2: That's the standard pattern. Yes, you can create your own
Speaker 2: custom err types too, just by defining a type with
Speaker 2: an error method.
Speaker 1: What about panics? When do they happen?
Speaker 2: Panics are for truly unexpected, unrecoverable situations at run time,
Speaker 2: things like index out of bounds, nil point, or to
Speaker 2: reference stuff that likely indicates a programming bug. You can
Speaker 2: trigger one explicitly with panic something went very wrong. Panics
Speaker 2: unwind the call stack.
Speaker 1: Can you stop a panic from crashing the whole program?
Speaker 2: Yes, using recover, but it only works if called directly
Speaker 2: inside a deferred function.
Speaker 1: Only inside a defer yep.
Speaker 2: If a panic occurs, deferred functions run. If one calls recover,
Speaker 2: the panic stops and recover returns the value past panic.
Speaker 2: Useful for preventing a single guarantine failure from taking down
Speaker 2: the server.
Speaker 1: For example, what are common causes of panics?
Speaker 2: Nil pointer to references are a big one. Trying to
Speaker 2: access a field on an uninitialized struct pointer or calling
Speaker 2: a method on a nil interface value. Also calling a
Speaker 2: function variable that's nil.
Speaker 1: Right, got to initialize things? What about adding more context
Speaker 1: to errors wrapping?
Speaker 2: Error wrapping is great for that. Using fmt dot air
Speaker 2: with the percent of a verb lets you wrap an
Speaker 2: existing air inside a new one, adding context fmt dot
Speaker 2: air opening configure dot percent of view air so.
Speaker 1: You can see the chain of error exactly.
Speaker 2: Errors dot unrapper gets you the underlying error. You can
Speaker 2: implement unwrap methods on custom error types too. This helps
Speaker 2: track the root.
Speaker 1: Cause and errors is an errors.
Speaker 2: Errors is Their target checks if any error in the
Speaker 2: chain matches the target error, either a specific sentinel value
Speaker 2: or type errors as are and targetvar tries to find
Speaker 2: an error of a specific type in the chain and
Speaker 2: assigns it to targetbar. Very useful for handling specific error
Speaker 2: types programmatically.
Speaker 1: Okay, air handling patterns are clear. What about stack traces?
Speaker 1: Seeing the call sequence.
Speaker 2: The runtime BBUG package helps there, debug stack returns the
Speaker 2: current stack trace, debug dot print stack prints. It invaluable
Speaker 2: when debugging tricky errors or panics.
Speaker 1: Definitely okay. Let's move on to a newer feature, generics.
Speaker 1: What problem do they solve?
Speaker 2: Before generics, writing functions or types that worked with multiple
Speaker 2: unrelated types often involved using interface any and lots of
Speaker 2: type assertions, which was cumbersome and less type safe.
Speaker 1: Like writing a keys function for a map that could
Speaker 1: work for maps with string keys or in keys.
Speaker 2: Exactly generics, let you write that function once using type parameters.
Speaker 2: You declare them in square brackets funk KEYSK comparable v
Speaker 2: any any m map kV k k.
Speaker 1: Comparable v any. Those are type parameters.
Speaker 2: With constraints right, K and V are the type parameters.
Speaker 2: Comparable is a constraint, meaning K must be a type
Speaker 2: that can be compared, like map keys need to be
Speaker 2: any means V can be any. Type. Constraints limit what
Speaker 2: types can be used.
Speaker 1: How does the compiler know what K and V are
Speaker 1: when you call the function.
Speaker 2: Often through type inference based on the arguments. If you
Speaker 2: pass a map string in, it figures out P is
Speaker 2: string and V isnt. Sometimes you might need to specify
Speaker 2: them explicitly key string in my map.
Speaker 1: Can you have multiple parameters and complex constraints?
Speaker 2: Yep, multiple type parameters, constraints defined using interfaces, specifying required methods,
Speaker 2: combining constraints with n using to allow underlying types type
Speaker 2: my end int could satisfy INT. The constraints package offers
Speaker 2: predefined ones like ordered integer, float, and.
Speaker 1: You can define generic types too, like strucks, dot type,
Speaker 1: no t any structure Absolutely.
Speaker 2: Generic types are very useful even with generics. Sometimes you
Speaker 2: might still need type assertions inside the generic function if
Speaker 2: you need to access type specific behavior not covered by
Speaker 2: the constraints.
Speaker 1: Generics sound powerful for reusable, type safe code. Now concurrency
Speaker 1: Goh's famous for this.
Speaker 2: It is important distinction. First. Concurrency is about managing multiple tasks,
Speaker 2: maybe interleaving them. Parallelism is about executing multiple tasks simultaneously.
Speaker 2: Go makes concurrency easy. Their routines right guroutines are lightweight
Speaker 2: concurrent functions. You launch one just by putting go before
Speaker 2: function call to go. My funk very cheap to create
Speaker 2: thousands of them.
Speaker 1: And the go run times schedules them onto OS threads.
Speaker 2: Yeah, the ghost scheduler manages multiplexing groutines onto a smaller
Speaker 2: pool of OS threads, handling things like work sharing and
Speaker 2: stealing to keep cores busy. Developers have some control via
Speaker 2: runtime dot gegomax prosess, but mostly it just.
Speaker 1: Works okay, easy to start concurrent tasks. How do they
Speaker 1: talk to each other?
Speaker 2: Channels? Channels are the idiomatic way for guroteins to communicate safely.
Speaker 2: They're typed conduits each each make caint you send with
Speaker 2: change value and receive with value.
Speaker 1: And they're syncretous by default, meaning send blocks until receive
Speaker 1: and vice versa exactly.
Speaker 2: For unbuffered channels, make chant ind it forces synchronization. They
Speaker 2: operate fifo. You can also create buffered channels move fee,
Speaker 2: make chan string ten.
Speaker 1: Buffer channels let the sender proceed without a receiver immediately.
Speaker 2: Available up to the buffer capacity. Yes, sends block only
Speaker 2: if the butter is full, receives block only if it's empty.
Speaker 1: How do you receive multiple values?
Speaker 2: For range YEP for value rained loops until the channel
Speaker 2: is closed to listen on multiple channels at once you
Speaker 2: use select.
Speaker 1: Select weights for the first channel.
Speaker 2: That's ready right, select case vowel netwuble one cases, envel, default,
Speaker 2: default if multiple are ready, uses one randomly. The default
Speaker 2: case makes the select non blocking channels ensure only one
Speaker 2: guaranteine receives each value sent. They're not message ques in
Speaker 2: that sense.
Speaker 1: Can you restrict channels to only sending or only receiving?
Speaker 2: Yes, UNI directional channels, chantunt is send only, chance string
Speaker 2: is receive only. Good for API clarity and type safety?
Speaker 1: How do you signal you're done sending on a channel?
Speaker 2: Receivers can detect a closed channel using the comma act
Speaker 2: idiom value ok on. If ok is false, the channel
Speaker 2: is closed and value is the zero value for the type.
Speaker 1: What happens if you close an already closed channel or
Speaker 1: send on a closed channel.
Speaker 2: Panic Gotta be careful. Often you sink once or a
Speaker 2: dedicated don channel pattern to ensure safe single closure.
Speaker 1: Okay guarantines and channels for concurrency. What about managing the
Speaker 1: life cycle of these operations, especially across APIs context exactly?
Speaker 2: The context package is key for managing request life cycles, passing,
Speaker 2: cancelation signals, deadlines, and request scoped values across API boundaries
Speaker 2: and between guarantines.
Speaker 1: What are the core parts?
Speaker 2: Context dot background is the root empty context context dot
Speaker 2: todo is a placeholder. Then you derive context using with cancel,
Speaker 2: with deadline, with timeout, and with value.
Speaker 1: And the context interface itself.
Speaker 2: It is done a channel closed when the context is
Speaker 2: canceled or times out. Aircast returns the reason for cancelation,
Speaker 2: Context canceled or context deadline exceeded. Deadline tells you the
Speaker 2: time it will be canceled, and value key retrieves associated values.
Speaker 1: How do you attach values.
Speaker 2: Context with value parent ctx key value. The key should
Speaker 2: be comparable, often a custom unexported type to avoid collisions
Speaker 2: between packages. Using the same string key value searches up
Speaker 2: the context tree.
Speaker 1: For the key and cancelation.
Speaker 2: How does that work with cancel Parent Ctx returns a
Speaker 2: new context and a cancel function. Calling cancel closes the
Speaker 2: done channel that context and all contexts derived from it.
Speaker 1: So gortein should listen on ctx.
Speaker 2: Done yes, usually in a select statement select case natchy
Speaker 2: ctx done, return ctx dot dr. It's crucial to defer
Speaker 2: cancel right after getting the cancel function to ensure resources
Speaker 2: are cleaned up. Timeouts and deadlines are just automatic ways
Speaker 2: to call.
Speaker 1: Cancel context seems essential for robust concurrent systems. How about
Speaker 1: coordinating guarantines more directly synchronization.
Speaker 2: The sink package has tools for that. Sinc weightgroup is
Speaker 2: common for waiting for a group of gorgutines to finish.
Speaker 1: How does it work?
Speaker 2: You call WG dot ad DN for ngorutines. Each guarantine
Speaker 2: calls WG done when finished, The main guarantine calls WG weight,
Speaker 2: which blocks until the counter reaches zero. Need to be
Speaker 2: careful not to misuse ad or done can cause deadlocks
Speaker 2: or panics.
Speaker 1: Is there something simpler for handling groups and errors?
Speaker 2: Yes, go lang dot organ sensor group. It manages a group,
Speaker 2: propagates context cancelation, and returns the first error encountered by
Speaker 2: any groatine in the group. Often simpler than managing weight,
Speaker 2: group and error channels manually.
Speaker 1: What about protecting shared data from raise conditions?
Speaker 2: That's sync mutex a mutual exclusion lock mutext dot lock
Speaker 2: before accessing shared data. Mutex dot unlock afterwards insures only
Speaker 2: one gorotine can be in the critical section at a time.
Speaker 2: Don't copy utext's after first use. Trilock provides a non.
Speaker 1: Blocking attempt and sync once.
Speaker 2: Guarantees of function runs exactly once, even with concurrent calls
Speaker 2: to its disdue method. Perfect for thread safe lazy initialization
Speaker 2: like we mentioned for safely closing channels.
Speaker 1: Good set of sync tools. Okay, last big area. Interacting
Speaker 1: with the filesystem.
Speaker 2: GO has several packages. Path filepath is for a platform
Speaker 2: independent path manipulation, join xt der base and the AS
Speaker 2: package that's for direct OS interaction. M adder ameshturl creates
Speaker 2: parents to create files, open file, more control, remove all, recursive, delete,
Speaker 2: stat file info, read file reads, whole file write file
Speaker 2: reader also handles OS signals like interrupt.
Speaker 1: What about the eye box package that's.
Speaker 2: A newer abstraction layer for filesystems defines interfaces like fs,
Speaker 2: dot FS and f smock file allows different filesystems implementations
Speaker 2: like the real OS filesystem via OS dot d S
Speaker 2: or in memory ones for testing using fistess dot map,
Speaker 2: fs FS walkdor is great for traversing directory trees and.
Speaker 1: Embedding files directly into the binary.
Speaker 2: That's the embed package. Use the GO dot embed pattern
Speaker 2: directive above a variable of type string, byte or mbed
Speaker 2: dot f s. The compiler bundles the specified files or directories,
Speaker 2: dot embed dot f S implements FS dot f S
Speaker 2: so you can use the standard filesystem APIs to read
Speaker 2: embedded files. Super handy for distributing assets with your app.
Speaker 1: Wow. Okay, we've really covered the core of Go, from
Speaker 1: structure and basics to concurrency and fileio.
Speaker 2: Absolutely, it's a journey through modules, packages, types, variables, control flow, functions, interfaces, errors, generics, guaratines, channels, context, sync,
Speaker 2: and the filesystem. Hopefully you have a clearer picture now
Speaker 2: of how these pieces fit together and Go.
Speaker 1: Yeah, definitely, and hopefully some of those specifics, like the
Speaker 1: implicit interfaces or how gorteines work with the scheduler or
Speaker 1: even embedding files. Hopefully those details spark some curiosity for
Speaker 1: you listening Understanding those often leads to using the language
Speaker 1: much more effectively.
Speaker 2: And this deep dive is really just a starting point.
Speaker 2: Go is a big language, always evolving. We definitely encourage
Speaker 2: you to take this foundation and explore further, write some code,
Speaker 2: dig into concurrency or generics if that interested you. The
Speaker 2: official docs in the community are great resources.
Speaker 1: So what aspects are you now curious about? What kinds
Speaker 1: of problems might you try solving with Go? Using these fundamentals.
Speaker 2: Yeah. Good Questions to ponder.
Speaker 3: In a final thought, consider how goes designs that focus
Speaker 3: on concurrency, the practical error handling, the efficient compilation. How
Speaker 3: all that contributes to its success in modern software. How
Speaker 3: might I understanding these GO fundamentals change how you approach
Speaker 3: building software, even if you're using a different language. Something
Speaker 3: to think about.