Hey there! ๐Ÿ‘‹ Ready to make our booking system interactive? This is where the fun really begins - we're going to let users actually book tickets!

Part 1: Getting User Input ๐ŸŽฏ

First, let's create a structure to hold our user's data:

type UserData struct {
    firstName       string
    lastName        string
    email          string
    numberOfTickets uint
}

Think of this struct as a form that users need to fill out. Just like in real life, we need their name, email, and how many tickets they want!

Understanding Structs in Go: Building Our User Form ๐Ÿ“

Hey there! Let's talk about something super cool in Go called structs. You know how when you fill out a form in real life, it has different boxes for your name, email, and other information? Well, a struct in Go is exactly like that digital form!

What's a Struct? ๐Ÿค”

You know how when you fill out a form, all the information belongs together? That's exactly what we're doing here with our UserData struct!

type UserData struct {
    firstName       string
    lastName        string
    email          string
    numberOfTickets uint
}

Imagine you're working at the registration desk for our English course. Instead of having loose pieces of paper with different information scattered around, you have a nicely organized form where everything about a person is kept together.

If this were a paper form, it would look like:

TICKET BOOKING FORM
------------------
First Name: ___________
Last Name:  ___________
Email:      ___________
Tickets:    ___________

Let's Break It Down! ๐Ÿ”

1. The type Keyword

type UseData

Let's talk about this type keyword that appears before our UserData struct. Think of type like introducing a new friend to your group - you're saying "Hey everyone, meet UserData!"

Here's what's really happening: When we use type, we're telling Go "I want to create my own custom data type." It's like creating a new template or blueprint that we can use throughout our program.

Imagine you're designing a form for your ticket booking system. Instead of having separate pieces of paper for name, email, and tickets, you create one standard form that combines all these fields. That's exactly what we're doing with type UserData!

Without type, we couldn't give our struct a name, and we'd have to write out the entire structure every time we wanted to use it - that would be like drawing a new form from scratch every time someone wants to book a ticket!

It's the difference between saying:

  1. "Here's our standard booking form" (using type)

  2. "Let me create a new form design every time" (not using type)

Pretty neat, right? It's all about making our code more organized and reusable! ๐ŸŽฏ

2. The Fields

Inside the curly braces { }, we list all the information we need to collect:

firstName       string
lastName        string
email          string
numberOfTickets uint

Each field is like a box on our form:

  • firstName string: A box for their first name

  • lastName string: A box for their last name

  • email string: A box for their email address

  • numberOfTickets uint: A box for how many tickets they want (we use uint because you can't book a negative number of tickets! ๐Ÿ˜‰)

Using Our Struct in Action! ๐Ÿ’ซ

Here's how we can create a new "form" (struct instance):

// Creating a new booking
var booking = UserData{
    firstName:       "John",
    lastName:        "Smith",
    email:          "john@example.com",
    numberOfTickets: 2,
}

Or think of it this way:

TICKET BOOKING FORM
------------------
First Name: John
Last Name:  Smith
Email:      john@example.com
Tickets:    2

Why Use a Struct? ๐ŸŽฏ

1. Organization ๐Ÿ“

Imagine if we didn't use a struct - it would be like having loose papers everywhere! We'd need separate slices for each piece of information:

var firstName string
var lastName string
var email string
var tickets uint

That would be a mess! ๐Ÿ˜ฑ What if we accidentally mix up the order? What if the email at index 5 doesn't actually belong to the person whose name is at index 5? Using a struct is like using a stapler to keep all of a person's information together - it can't get mixed up!

Plus, when we pass booking information around in our program, we can pass it as a single unit. It's like handing someone a complete form instead of individual pieces of paper. Clean, organized, and professional! ๐ŸŽฏ

2. Type Safety ๐Ÿ”’

Think of type safety like having specialized slots in a form:

  • The name slot only accepts text

  • The ticket slot only accepts numbers

  • The email slot must be in email format

It's like having a smart form that won't let you:

  • Put letters in the phone number field

  • Put numbers in the name field

  • Submit without filling required fields

// Create multiple users with the same structure
student1 := UserData{firstName: "John", lastName: "Smith", ...}
student2 := UserData{firstName: "Jane", lastName: "Doe", ...}
student3 := UserData{firstName: "Bob", lastName: "Johnson", ...}
// This works fine

user.numberOfTickets = 2

// This would cause an error - Go protects us!

user.numberOfTickets = "two" // Error: cannot use "two" (type string) as type uint

It's like having a standard registration form where:

  • Every student fills out the same fields

  • The data is stored consistently

  • You can easily process all registrations the same way

Think of it as the difference between:

  • Having students write their info on blank papers (chaos!) ๐Ÿ“

  • Having them fill out standardized forms (organized!) ๐Ÿ“‹

Remember: Using structs is like being an organized teacher with a perfect filing system - everything has its place, and you can find what you need instantly! ๐ŸŽฏ

Pro Tips! ๐Ÿ’ก

1. Naming Conventions

  • Struct names are usually in PascalCase (UserData)

  • Field names are in camelCase (firstName)

2. Organization

  • Keep related fields together

  • Order fields logically (name fields together, contact info together)

3. Memory Efficiency

  • Go will optimize memory usage based on field order

  • Similar types (like strings) are usually grouped together

Ready to use our struct to store some actual bookings? Let me know if anything's unclear - structs are super important in Go, so we want to make sure you're comfortable with them! ๐Ÿš€

Now, let's create a function to get user input:

3. Reusability โ™ป๏ธ

Think of a struct like a cookie cutter - once you create the template, you can use it again and again:

func getUserInput() (string, string, string, uint) {
    var firstName string
    var lastName string
    var email string
    var userTickets uint

    // Getting the first name
    fmt.Println("\n๐Ÿ‘ค Enter your first name: ")
    fmt.Scan(&firstName)

    // Getting the last name
    fmt.Println("๐Ÿ‘ค Enter your last name: ")
    fmt.Scan(&lastName)

    // Getting email
    fmt.Println("๐Ÿ“ง Enter your email address: ")
    fmt.Scan(&email)

    // Getting number of tickets
    fmt.Println("๐ŸŽŸ๏ธ How many tickets would you like to book? ")
    fmt.Scan(&userTickets)

    return firstName, lastName, email, userTickets
}

Understanding the getUserInput Function ๐ŸŽฏ

Hey there! Let's dive deep into our getUserInput function. This is where we interact with our users, kind of like having a conversation with them! Let's break it down piece by piece.

The Function Signature ๐Ÿ“

func getUserInput() (string, string, string, uint) {

Look at this first line - it's pretty interesting! Let me explain what's happening here:

  • func getUserInput() - We're creating a function named getUserInput

  • The empty parentheses () mean we're not taking any parameters

  • Those types at the end (string, string, string, uint) tell Go that we're returning 4 values:

    • Two strings for the first and last names

    • One string for the email

    • One unsigned integer (uint) for the number of tickets

It's like telling Go: "Hey, I'm going to collect four pieces of information and give them back to you!"

Declaring Our Variables ๐Ÿท๏ธ

var firstName string
var lastName string
var email string
var userTickets uint

This part is like preparing empty boxes to store our information:

  • Each var line creates a new variable

  • string variables can hold text (like names and email)

  • uint (unsigned integer) is perfect for tickets because you can't book a negative number of tickets!

Pro Tip! ๐Ÿ’ก We could write this more concisely as:

var (
    firstName, lastName, email string
    userTickets uint
)

Getting User Input ๐ŸŽค

Let's look at how we get each piece of information:

fmt.Println("\n๐Ÿ‘ค Enter your first name: ")
fmt.Scan(&firstName)

This is where the magic happens! Let's break it down:

  1. fmt.Println() displays our prompt to the user

  2. The \n adds a new line before the prompt (makes it look nicer!)

  3. fmt.Scan(&firstName) is really interesting:

    • It waits for the user to type something and press Enter

    • The & symbol says "put the answer in this variable's location in memory"

    • It's like saying "store what the user types right here!"

Why Use &? ๐Ÿค”

Think of & like a mailbox address. We're telling Go: "When the user types something, put it in the mailbox at this address." Without &, we'd just be working with a copy of the address, not the actual mailbox!

The Pattern Repeats ๐Ÿ”„

We do the same thing for each piece of information:

fmt.Println("๐Ÿ‘ค Enter your last name: ")
fmt.Scan(&lastName)

fmt.Println("๐Ÿ“ง Enter your email address: ")
fmt.Scan(&email)

fmt.Println("๐ŸŽŸ๏ธ How many tickets would you like to book? ")
fmt.Scan(&userTickets)

Notice how the pattern is the same, but we use different emojis and prompts to make it clear what we're asking for!

Returning the Values ๐Ÿ”™

return firstName, lastName, email, userTickets

Finally, we package up all our collected information and send it back to wherever this function was called from! Think of it like this:

When somebody calls getUserInput(), like in our main() function:

firstName, lastName, email, userTickets := getUserInput()

The values get returned in exactly this order:

  1. firstName goes to the first variable

  2. lastName goes to the second variable

  3. email goes to the third variable

  4. userTickets goes to the fourth variable

It's like a delivery service where:

  1. We collect all the items (user inputs)

  2. Package them in a specific order (matching our function signature)

  3. Send them back to the caller (main function in this case)

  4. The caller unpacks them into their own variables in the same order

For example:

// In main function
firstName, lastName, email, userTickets := getUserInput()
// Now we can use these values:
fmt.Printf("\n๐ŸŽ‰ Thank you %v %v for booking %v tickets! You'll receive a confirmation email at %v\n", firstName, lastName, userTickets, email)

This is how data flows in our program:

  1. getUserInput() collects data from the user

  2. Returns that data to main()

  3. main() can then use that data for booking tickets, validation, etc.

Remember: The order of variables when returning and receiving must match exactly - it's like making sure each package goes to the right destination! ๐Ÿ“ฆ

Let's Run Our Booking System! ๐Ÿš€

Ready to see our booking system in action? Let's run it and watch the magic happen! Fire up your terminal and let's test it out:

Welcome to Triangle.Technology English Course Booking System!
We have total of 30 tickets and 30 are still available.
Get your tickets here to attend

๐Ÿ‘ค Enter your first name:
John

๐Ÿ‘ค Enter your last name:
Smith

๐Ÿ“ง Enter your email address:
john@example.com

๐ŸŽŸ๏ธ How many tickets would you like to book?
2

๐ŸŽ‰ Thank you John Smith for booking 2 tickets! You'll receive a confirmation email at john@example.com
28 tickets remaining for the course

In Go, the order of function declarations is not important. You can place the main() function at the beginning or end of the file - it doesn't matter. This is different from languages like C/C++ where functions must be declared before they can be used. The Go compiler scans the entire file to find all functions before compilation, and when the program runs, it will always start with the main() function regardless of its location in the file.

However, it's considered good practice to organize your functions in a logical order for better code readability and maintainability. For example, you might want to group related functions together, or place helper functions near the main functions that use them. This makes it easier for other developers (or yourself in the future) to understand the flow and structure of your code.

We've overlooked something important here - our program is working with strings throughout the code. Just like how we needed to import "fmt" to use formatting functions, we should also import the "strings" package when dealing with string operations in Go. Let's update our import section to include both packages:

Want to try it yourself? Just:

  1. Save all our code in main.go

  2. Open your terminal

  3. Navigate to your project directory

  4. Type go run main.go

  5. Start booking tickets!

Part 2: Validating User Input โœ…

Now, here's something super important - never trust user input! Let's add some validation:

func validateUserInput(firstName string, lastName string, email string, userTickets uint) (bool, bool, bool) {
    // Check if names are at least 2 characters long
    isValidName := len(firstName) >= 2 && len(lastName) >= 2
    
    // Check if email contains @ symbol
    isValidEmail := strings.Contains(email, "@")
    
    // Check if ticket number is valid
    isValidTicketNumber := userTickets > 0 && userTickets <= remainingTickets

    return isValidName, isValidEmail, isValidTicketNumber
}

Deep Dive: Understanding Our Validation Function ๐Ÿ”

Hey there! Let's break down our validation function piece by piece. You know how a bouncer at a club checks IDs? Well, this function is like our program's bouncer - making sure everyone (or in this case, every input) meets our requirements! ๐Ÿ˜„

The Function Signature ๐Ÿ“

Let's talk about one of the most interesting parts of our validation function - its signature!

func validateUserInput(firstName string, lastName string, email string, userTickets uint) (bool, bool, bool)

First, let's understand what we have here:

1. Function Parameters (Input) ๐Ÿ“ฅ

func validateUserInput(firstName string, lastName string, email string, userTickets uint)

Think of parameters like ingredients you need to make a recipe:

  • Parameters are the values that you pass INTO a function

  • They're like inputs that the function needs to do its job

  • Each parameter has a name AND a type (like ingredients with their measurements)

In our function, we have 4 parameters:

  1. firstName (type: string) - Text for first name

  2. lastName (type: string) - Text for last name

  3. email (type: string) - Text for email address

  4. userTickets (type: uint) - Number of tickets (unsigned integer, meaning positive numbers only)

Example of using these parameters:

validateUserInput("John", "Doe", "john@example.com", 2)

2. Return Values (Output) ๐Ÿ“ค

(bool, bool, bool)

Now, let's talk about what our function gives back (returns):

  • bool is short for "boolean"

  • A boolean can only be true or false (like a yes/no answer)

  • Our function returns THREE booleans

Why three booleans? Because we're checking three different things:

  1. First boolean: Is the name valid? (true/false)

  2. Second boolean: Is the email valid? (true/false)

  3. Third boolean: Is the ticket number valid? (true/false)

Example of how the return values work:

isValidName, isValidEmail, isValidTicketNumber := validateUserInput("John", "Doe", "john@example.com", 2)
// might return: true, true, true

isValidName, isValidEmail, isValidTicketNumber := validateUserInput("J", "D", "invalid-email", 0)
// might return: false, false, false

Why 4 Parameters but 3 Returns? ๐Ÿค”

Great question! Here's why:

  • We need BOTH firstName and lastName to check if the name is valid

  • These two parameters are used together to produce ONE validation result

For example, think about password validation in a signup form. When you create a new password, you're always asked to type it twice, right? This is perfect to explain our concept:

// Example of password validation
func validatePassword(password string, confirmPassword string) (bool) {
    // Two inputs, but only one check: do they match?
    return password == confirmPassword
}

Just like how we need both password and confirmPassword to perform a single validation (checking if they match), we need both firstName and lastName to perform our single name validation. It's a "two inputs, one check" situation. In our case:

// Two name parameters combine for one name validation
isValidName := len(firstName) >= 2 && len(lastName) >= 2
  • This represents ONE concept: "Is the full name valid?"

  • Both names need to be valid for the overall name to be valid

  • It's like checking a complete name tag, not two separate pieces

Validation Check #1: Names ๐Ÿ‘ค

isValidName := len(firstName) >= 2 && len(lastName) >= 2

This line is doing some cool things:

  • len(firstName) counts the characters in the first name

  • len(lastName) counts the characters in the last name

  • >=2 makes sure each name has at least 2 characters

  • && means BOTH conditions must be true (like needing both ID AND ticket to enter a concert)

Example scenarios:

"John", "Doe"      // โœ… Both valid (lengths: 4, 3)
"J", "Doe"         // โŒ First name too short (lengths: 1, 3)
"John", "D"        // โŒ Last name too short (lengths: 4, 1)
"", ""             // โŒ Both empty (lengths: 0, 0)

Validation Check #2: Email ๐Ÿ“ง

isValidEmail := strings.Contains(email, "@")

This is like checking if a phone number has the right format:

  • strings.Contains() searches for one string inside another

  • We're looking for the "@" symbol, which every email must have

Example scenarios:

"john@example.com"   // โœ… Valid (contains @)
"john.example.com"   // โŒ Invalid (no @)
"@example.com"       // โœ… Valid (technically, though we might want stricter rules!)
"johnexample.com"    // โŒ Invalid (no @)

๐Ÿ’ก Pro Tip: In a real application, you might want more thorough email validation:

// More comprehensive email validation (example)
func isValidEmail(email string) bool {
    return strings.Contains(email, "@") &&
           strings.Contains(email, ".") &&
           len(strings.Split(email, "@")[0]) > 0 &&
           len(strings.Split(email, "@")[1]) > 3
}

This enhanced validation function is like having a detailed checklist:

  1. strings.Contains(email, "@") - Makes sure there's an "@" symbol (like making sure there's a street divider)

  2. strings.Contains(email, ".") - Checks for a dot (like checking for a city/state separator)

  3. len(strings.Split(email, "@")[0]) > 0 - Verifies there's text before the "@" (like making sure there's a recipient name)

  4. len(strings.Split(email, "@")[1]) > 3 - Ensures the domain part after "@" is long enough (like making sure the address has a proper city and zip code)

For example:

  • "user@domain.com" โœ… (Passes all checks)

  • "@domain.com" โŒ (No username part)

  • "user@.com" โŒ (Domain too short)

  • "user.domain.com" โŒ (Missing @)

While it's still not perfect (email validation can get REALLY complex!), it's much more robust than our basic check and will catch the most common invalid email formats that users might enter.

Validation Check #3: Ticket Numbers ๐ŸŽŸ๏ธ

isValidTicketNumber := userTickets > 0 && userTickets <= remainingTickets

This is like making sure someone doesn't try to take more cookies than what's in the jar! We check two things:

  • userTickets > 0: Can't book zero or negative tickets

  • userTickets <= remainingTickets: Can't book more tickets than available

Example scenarios:

// Assuming remainingTickets = 5
userTickets = 3  // โœ… Valid (0 < 3 <= 5)
userTickets = 0  // โŒ Invalid (can't book 0 tickets)
userTickets = 6  // โŒ Invalid (can't book more than remaining)
userTickets = -1 // โŒ Invalid (no negative tickets!)

Putting It All Together ๐ŸŽฏ

The beauty of Go is that it can return multiple values from a function! Let's break this down:

return isValidName, isValidEmail, isValidTicketNumber

Think of this like a teacher grading three different parts of your test and giving you three separate scores. Each value is either true (passed) or false (failed).

1. The Simplest Example ๐ŸŒฑ

Let's see this in action with the most basic example possible:

// Our validation function
func validateUserInput(firstName string, lastName string, email string, userTickets uint) (bool, bool, bool) {
    isValidName := len(firstName) >= 2 && len(lastName) >= 2
    isValidEmail := strings.Contains(email, "@")
    isValidTicketNumber := userTickets > 0 && userTickets <= remainingTickets
    
    return isValidName, isValidEmail, isValidTicketNumber
}

// In main function
func main() {
    // Example input
    firstName := "John"
    lastName := "Doe"
    email := "john@example.com"
    userTickets := uint(2)
    
    // Receiving all three return values
    nameValid, emailValid, ticketsValid := validateUserInput(firstName, lastName, email, userTickets)
    
    // Let's print each result
    fmt.Printf("Name validation: %v\n", nameValid)        // true
    fmt.Printf("Email validation: %v\n", emailValid)      // true
    fmt.Printf("Tickets validation: %v\n", ticketsValid)  // true
}

Imagine our program is like a helpful ticket counter clerk processing a booking request. When a customer (let's call him John Doe) comes up to book 2 tickets, our clerk needs to verify three things:

First, we have a validation system (our validateUserInput function) that acts like a checklist. It takes John's information (his first name "John", last name "Doe", email "john@example.com", and his request for 2 tickets) and checks three important things at once: Are his names long enough? Is his email properly formatted? Is his ticket request reasonable?

Then, just like a clerk marking off boxes on a form, our program runs these checks simultaneously. Each check returns a simple "yes" (true) or "no" (false). It's like having three checkboxes: โœ“ for the name, โœ“ for the email, and โœ“ for the ticket amount.

In our example, when John submits his information, our program receives three green lights: his name is valid (both "John" and "Doe" are long enough), his email looks correct (it has an @ symbol), and his ticket request for 2 tickets is reasonable (it's more than 0 and we have enough tickets available). The program then displays these results one by one, confirming that everything checks out perfectly for John's booking request! ๐ŸŽซ

Think of it as getting three thumbs up ๐Ÿ‘๐Ÿ‘๐Ÿ‘ all at once, each one confirming a different aspect of the booking is good to go!

2. How It Works ๐Ÿ”„

Think of it like opening three packages at once:

nameValid, emailValid, ticketsValid := validateUserInput(...)
  • nameValid gets the first returned value (isValidName)

  • emailValid gets the second returned value (isValidEmail)

  • ticketsValid gets the third returned value (isValidTicketNumber)

You might notice that getUserInput() and validateUserInput() have different return patterns. Think of getUserInput() like filling out a form where each field is different - you're collecting a first name (string), last name (string), email (string), and number of tickets (uint). It's like gathering different types of ingredients for a recipe.

On the other hand, validateUserInput() is more like a checklist - you're getting yes/no answers (booleans) for each validation check. It's similar to a quality control inspector checking multiple boxes: โœ“ name is valid, โœ“ email is valid, โœ“ ticket number is valid.

This is why we use them differently in our code:

// Getting different types of data
firstName, lastName, email, tickets := getUserInput()

// Getting multiple yes/no answers
nameValid, emailValid, ticketsValid := validateUserInput(...)

Both return multiple values, but they serve different purposes - one collects various pieces of information, while the other performs multiple validity checks! ๐ŸŽฏ

You might also notice another interesting difference in how we name our variables when receiving returned values. In Go, the variable names at the receiving end (in main) don't need to match the names used inside the functions!

For example, inside getUserInput() we use firstName, lastName, email, userTickets, but when calling it we could use different names like fname, lname, userEmail, numTickets := getUserInput(). Similarly, while our validation function returns isValidName, isValidEmail, isValidTicketNumber, we can receive them as nameValid, emailValid, ticketsValid.

Think of it like receiving mail - what matters is the order of the packages (values), not what you name them when they arrive. Go only cares about the types and order matching, giving you the flexibility to use variable names that make the most sense in your current context! ๐Ÿ“ซ

The key is maintaining consistent and meaningful names within each context, even if they differ across functions. It's like having a nickname at home and a formal name at work - both refer to you, just in different contexts! ๐ŸŽฏ

Part 3: Bringing It All Together in Main ๐ŸŽฏ

Hey there! Now comes the exciting part - let's see how everything works together in our main function! Think of this like being the conductor of an orchestra, where we coordinate all our different pieces to create beautiful music. ๐ŸŽต

func main() {
    greetUsers()

    for {
        firstName, lastName, email, userTickets := getUserInput()
        isValidName, isValidEmail, isValidTicketNumber := validateUserInput(firstName, lastName, email, userTickets)

        if isValidName && isValidEmail && isValidTicketNumber {
            bookTicket(userTickets, firstName, lastName, email)
            
            // Show first names of all bookings
            firstNames := getFirstNames()
            fmt.Printf("\n๐ŸŽ‰ Current Bookings: %v\n", firstNames)

            // Check if we're sold out
            if remainingTickets == 0 {
                fmt.Println("\n๐ŸŽซ Sorry, we're all sold out! Come back next time!")
                break
            }
        } else {
            if !isValidName {
                fmt.Println("\nโŒ Names must be at least 2 characters long")
            }
            if !isValidEmail {
                fmt.Println("\nโŒ Email must contain @ symbol")
            }
            if !isValidTicketNumber {
                fmt.Printf("\nโŒ We only have %v tickets remaining. Please try again.\n", remainingTickets)
            }
        }
    }
}

Quick Note About Our Functions ๐Ÿ”

Hey there! Before we continue, I noticed you might be wondering about some functions we're using in main() but haven't created yet.

func main() {
    greetUsers()        // First, welcome the customer
    for {
        // Get and validate user input...
        bookTicket()    // Then, process their booking
        getFirstNames() // Finally, update the guest list
    }
}

Just like our getUserInput() and validateUserInput() functions, we're also using:

  • greetUsers(): Welcomes users and shows ticket availability

  • bookTicket(): Handles the actual ticket booking process

  • getFirstNames(): Keeps track of who's booked tickets

We jumped straight into main() first so you could see the big picture - like looking at a blueprint before building a house! Don't worry though, we'll create each of these functions step by step. The reason we started with main() is to help you understand how everything fits together, even though in practice we'd need to define these functions before using them in main().

Shall we continue exploring these functions in detail? ๐Ÿš€

The Welcome Function: greetUsers() ๐Ÿ‘‹

Hey there! Let's talk about our greetUsers() function - it's like the friendly receptionist of our booking system! While it might seem simple, it plays a crucial role in setting the tone for our user experience.

func greetUsers() {
    // Welcome message
    fmt.Printf("Welcome to %v Booking System!\n", conferenceName)
    
    // Show ticket availability
    fmt.Printf("We have total of %v tickets and %v are still available.\n", 
        conferenceTickets, remainingTickets)
    
    // Call to action
    fmt.Println("Get your tickets here to attend")
}

You might have noticed that in our greetUsers() function, we didn't declare variables before using them like we did in other functions (remember firstName, lastName, email, and userTickets in getUserInput()?). But these variables look familiar, right? conferenceName, conferenceTickets, and remainingTickets - we saw them in our earlier lesson on Project Setup and Basic Structure!

const conferenceName = "Triangle.Technology English Course"
const conferenceTickets = 30
var remainingTickets uint = 30

Now, let's make an important distinction. Instead of keeping these variables inside main(), we've moved them to the package level (outside any function), making them global. Here's how our code structure looks:

// These are package level (global) variables
const conferenceName = "Triangle.Technology English Course"
const conferenceTickets = 30
var remainingTickets uint = 30
var bookings = make([]UserData, 0)

// Not like this (inside main):
func main() {
    const conferenceName = "Triangle.Technology English Course" // Don't do this!
    // ...
}

Why did we do this? Well, think of global variables like a bulletin board in an office - everyone can see and use them. We need these variables (conferenceName, conferenceTickets, remainingTickets) to be accessible by multiple functions because they represent our system's shared state. On the other hand, variables like firstName and lastName are like personal notes - they're only needed within their specific function!

Making Smart Choices About Variable Scope ๐Ÿค”

Just remember - while global variables are convenient, we should use them sparingly and only for values that truly need to be shared across multiple functions! ๐ŸŽฏ

Let's play "what if" for a moment! What if we moved conferenceName to be local or made firstName global? Here's why these would be bad ideas:

If we made conferenceName local to greetUsers():

func greetUsers() {
    var conferenceName = "Triangle.Technology English Course"  // Local now!
    fmt.Printf("Welcome to %v Booking System!\n", conferenceName)
}

Oops! Now other functions can't access the course name. What if our bookTicket() function needs to include the course name in the confirmation message? We'd have to pass it around everywhere or duplicate it in multiple places. Not very maintainable!

On the flip side, if we made firstName global:

var firstName string  // Global now!

func getUserInput() {
    fmt.Scan(&firstName)
}

Yikes! This would be like writing everyone's name on the office bulletin board just to check their spelling! It's unnecessary and could lead to confusion - what happens when multiple users are booking tickets? Whose first name is it anyway?

Remember: Keep things local unless they truly need to be shared. It's like the difference between a community bulletin board and your personal notepad! ๐Ÿ“

The Infinite Loop ๐Ÿ”„

for {
    // Code here runs forever until we break
}

See that simple for without any conditions? That's Go's way of saying "keep doing this forever"! It's like a store that stays open until we explicitly close it. Why do we want this? Because we want our booking system to keep taking reservations until we run out of tickets!

Getting User Information ๐Ÿ“

firstName, lastName, email, userTickets := getUserInput()

In Go, this line demonstrates multiple value assignment using the short declaration operator (:=). Our getUserInput() function returns four values, and we're capturing all of them at once.

Let's break down what's happening:

  • The function returns 4 values in order: firstName, lastName, email, userTickets

  • The := operator both declares these variables and assigns values to them

  • Each variable on the left matches the corresponding return value on the right

It's equivalent to writing:

var firstName string
var lastName string
var email string
var userTickets uint

firstName, lastName, email, userTickets = getUserInput()

Think of it this way: In many other languages, you might need multiple lines to get multiple values from a function. Go lets us do it in one elegant line - it's like catching multiple items at once, with each value landing in its designated variable! ๐ŸŽฏ

Can We Change the Variable Names? ๐Ÿท๏ธ

Absolutely! The variable names are totally up to you. For example, you could write:

customerFirstName, customerLastName, customerEmail, ticketCount := getUserInput()

or even:

fName, lName, emailAddr, numTickets := getUserInput()

The key is making sure these match the order of values that getUserInput() returns. Speaking of which, let's look at our getUserInput() function:

func getUserInput() (string, string, string, uint) {
    // ... function code ...
    return firstName, lastName, email, userTickets
}

It's like a package delivery - the order matters! The first value returned goes to the first variable, second to second, and so on.

Pro Tip ๐Ÿ’ก: The names don't have to match between the function and where you use it. What matters is:

  • The number of variables (must be four in this case)

  • The types (string, string, string, uint)

  • The order (first return value goes to first variable, etc.)

That's why this would also work:

a, b, c, d := getUserInput()  // Works, but please use meaningful names! ๐Ÿ˜…

Remember: Always use clear, meaningful names that explain what the variables are for. Your future self (and other developers) will thank you! ๐Ÿ™

Validation Check โœ…

isValidName, isValidEmail, isValidTicketNumber := validateUserInput(firstName, lastName, email, userTickets)

Imagine you're working at a ticket counter, and you've got a checklist to verify customer information. That's exactly what this line does! ๐ŸŽŸ๏ธ

The Input Data We're Checking โœ๏ธ

Let's look at what we're passing into our validation function:

  • firstName: The customer's first name (like "John")

  • lastName: The customer's last name (like "Smith")

  • email: Where we'll send their ticket confirmation (like "john@email.com")

  • userTickets: How many tickets they want to book (like 2)

These four pieces of information came from our previous getUserInput() function - it's like having a form that the customer just filled out.

Understanding Function Calls: Parameters vs. Return Values ๐ŸŽฏ

Hey there! Let's dive into why some function calls work without arguments while others don't. This is a great question about Go's function behavior!

Let's Compare Our Functions ๐Ÿ”

// Function 1: getUserInput
func getUserInput() (string, string, string, uint) {
    // ... implementation
}

// Function 2: validateUserInput
func validateUserInput(firstName string, lastName string, email string, userTickets uint) (bool, bool, bool) {
    // ... implementation
}

Why This Works โœ…

firstName, lastName, email, userTickets := getUserInput()

Why This Doesn't Work โŒ

isValidName, isValidEmail, isValidTicketNumber := validateUserInput()

The Key Difference ๐Ÿ”‘

Think of it like a vending machine vs. a coffee maker:

getUserInput (Vending Machine) ๐ŸŽซ

  • Doesn't need anything from you

  • Just press the button (call the function)

  • Get your items (return values)

validateUserInput (Coffee Maker) โ˜•

  • Needs ingredients (parameters)

  • Can't make coffee without water and coffee beans!

  • Will give you an error if you try to run it empty

Technical Explanation ๐Ÿ› ๏ธ

1. Function Parameters

func validateUserInput(firstName string, lastName string, email string, userTickets uint)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                      These are REQUIRED parameters!

When a function declares parameters, you MUST provide them

  • It's like a contract: "I need this data to do my job"

2. Function Return Values

func getUserInput() (string, string, string, uint)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                    Just tells you what you'll get back

Return values just tell you what you'll receive

  • No input required!

Real World Example ๐ŸŒŽ

Think of it like two different types of forms:

1. getUserInput is like a Survey Creator

// Just creates a new blank form
newForm := createBlankForm()  // Needs no input!

2. validateUserInput is like a Form Checker

// Must have a form to check!
isValid := checkForm()        // โŒ Error! What form are we checking?
isValid := checkForm(form)    // โœ… Now we're checking a specific form

Error Messages You'd See ๐Ÿšจ

If you try to call validateUserInput without arguments:

isValidName, isValidEmail, isValidTicketNumber := validateUserInput()
// Error: not enough arguments in call to validateUserInput
//        have ()
//        want (string, string, string, uint)

Best Practice Example ๐Ÿ’ก

// Correct way to use these functions:
// 1. Get user input (needs no parameters)
firstName, lastName, email, userTickets := getUserInput()

// 2. Validate the input (needs all parameters)
isValidName, isValidEmail, isValidTicketNumber := validateUserInput(
    firstName,    // Required!
    lastName,     // Required!
    email,        // Required!
    userTickets,  // Required!
)

Remember! ๐ŸŽฏ

  1. Functions with parameters MUST receive those parameters

  2. Functions without parameters can be called empty ()

  3. Return values don't affect whether you need parameters or not

Think of it this way:

  • Parameters are what a function needs to do its job

  • Return values are what you get back after the job is done

Understanding Function Return Values and Multiple Assignment in Go ๐Ÿ”

Hey there! Let's talk about why we can't directly chain these functions together, even though it might seem logical at first glance. This is a great question about Go's function return values!

// What we want to do (but can't):
isValidName, isValidEmail, isValidTicketNumber := validateUserInput(getUserInput())

// What we have to do:
firstName, lastName, email, userTickets := getUserInput()
isValidName, isValidEmail, isValidTicketNumber := validateUserInput(firstName, lastName, email, userTickets)

Why It Doesn't Work ๐Ÿšซ

It's all about how Go handles multiple return values! Let's break it down:

1. Return Value Packaging

// getUserInput() returns 4 values:
func getUserInput() (string, string, string, uint)

When Go returns multiple values, it packages them as a single tuple-like unit. You can't automatically unpack this into separate arguments!

2. Function Arguments

// validateUserInput() expects 4 separate arguments:
func validateUserInput(firstName string, lastName string, email string, userTickets uint)

The function expects individual arguments, not a packed tuple of values.

Real-World Analogy ๐ŸŒŽ

Think of it like a package delivery:

getUserInput() returns a box with 4 items:

๐Ÿ“ฆ [firstName, lastName, email, userTickets]

validateUserInput() needs 4 separate items:

firstName โ†’ Slot 1
lastName  โ†’ Slot 2
email     โ†’ Slot 3
tickets   โ†’ Slot 4

You can't just put the whole box into one slot - you need to unpack it first!

Technical Explanation ๐Ÿ”ง

// This works:
a, b, c, d := getUserInput()    // Unpacks 4 return values into 4 variables
validateUserInput(a, b, c, d)   // Passes 4 separate arguments

// This doesn't work:
validateUserInput(getUserInput())  // Tries to pass packed return values as a single argument

Why Go Designed It This Way ๐Ÿค”

1. Clarity

  • Makes it clear exactly what values are being passed

  • Prevents hidden data flow

  • Makes debugging easier

2. Type Safety

  • Ensures each value is explicitly handled

  • Prevents accidental mismatches

3. Flexibility

  • Allows you to process intermediate values

  • Makes it easier to add logging or validation

The Right Way (With Examples) โœ…

// Good Practice:
firstName, lastName, email, userTickets := getUserInput()
// You can add debugging here if needed
fmt.Printf("Debug: Got input from user: %v %v\n", firstName, lastName)
// Now validate
isValid := validateUserInput(firstName, lastName, email, userTickets)

// Not Possible:
isValid := validateUserInput(getUserInput())  // Go will complain!

Remember! ๐Ÿ’ก

  1. Go functions returning multiple values create a single packed return value

  2. Function arguments are always separate, individual values

  3. You need to unpack multiple return values before using them as arguments

  4. This design choice promotes clearer, more maintainable code

Think of it as "explicit is better than implicit" - Go wants you to be clear about how you're handling your data!

The Validation Process ๐Ÿ”

When we call validateUserInput(), it's like passing this form to our supervisor who checks three things:

func validateUserInput(firstName string, lastName string, email string, userTickets uint) (bool, bool, bool) {
    isValidName := len(firstName) >= 2 && len(lastName) >= 2
    isValidEmail := strings.Contains(email, "@")
    isValidTicketNumber := userTickets > 0 && userTickets <= remainingTickets
    
    return isValidName, isValidEmail, isValidTicketNumber
}

The function returns three yes/no answers (bools):

  • isValidName: Are both names long enough? โœ…

  • isValidEmail: Does the email look legitimate? โœ…

  • isValidTicketNumber: Is the ticket request reasonable? โœ…

Real-World Example ๐ŸŒŸ

Let's see it in action:

// Example input:
firstName = "J"              // Too short!
lastName = "Smith"           // Good
email = "johnsmith.com"      // Missing @
userTickets = 50            // Too many tickets!

// After validation:
isValidName = false         // Because firstName is too short
isValidEmail = false        // Because email is missing @
isValidTicketNumber = false // Because 50 is more than remaining tickets

It's like a quality check system:

  • "J" โŒ - "Sorry, first name needs to be longer"

  • "johnsmith.com" โŒ - "Oops, that email doesn't have an @ symbol"

  • "50 tickets" โŒ - "We don't have that many tickets available!"

Why This Matters ๐ŸŽฏ

This validation is crucial because:

  1. We want valid names for our guest list

  2. We need a working email to send tickets

  3. We can't sell more tickets than we have!

Think of it like a bouncer at a club:

  • Checking IDs (validating names)

  • Verifying contact info (validating email)

  • Making sure there's space inside (validating ticket numbers)

Pro Tips! ๐Ÿ’ก

  • The order matters! When we get back our three boolean values:

isValidName, isValidEmail, isValidTicketNumber := validateUserInput(...)

They match exactly with the order in the return statement of our function.

  • We could name these variables differently:

nameOK, emailOK, ticketsOK := validateUserInput(...)

But using isValid makes it crystal clear what we're checking!

  • This is a great example of Go's multiple return values - instead of returning one big "valid/invalid" result, we get specific feedback about what might be wrong.

Remember: Good validation is like a good friend - it stops you from making mistakes before they become problems! ๐Ÿ›ก๏ธ

The Decision Point ๐Ÿ”€

if isValidName && isValidEmail && isValidTicketNumber {
    // Everything is valid! Process the booking
} else {
    // Something's wrong! Show error messages
}

Hey there! Let's break down this super important part of our code - the decision point. Think of it as our booking system's brain making crucial decisions! ๐Ÿง 

Understanding if-else in Go

The if-else statement is like a bouncer at a club making decisions:

  • If you meet ALL requirements, you get in (the if part)

  • If you fail ANY requirement, you stay out (the else part)

It's that simple! But let's dig deeper...

The Magic of && (AND Operator)

See those && symbols? They're super important! In Go (and most programming languages), && means "AND". Think of it this way:

isValidName && isValidEmail && isValidTicketNumber

This is like a checklist where EVERYTHING must be true:

  • โœ… Is the name valid? AND

  • โœ… Is the email valid? AND

  • โœ… Is the ticket number valid?

If ANY of these is false, you're not getting in!

Pro Tips for Working with Conditions ๐Ÿ’ก

Here's a cool thing about how Go handles multiple conditions with &&: it checks them from left to right and stops as soon as it finds a false - this is called "short-circuit evaluation". Think of it like a security guard checking IDs at an exclusive event. If your name isn't on the list (first check is false), they don't even bother checking your ID or dress code! It's both efficient and smart. For example, in isValidName && isValidEmail && isValidTicketNumber, if isValidName is false, Go doesn't waste time checking the other conditions. That's why it's good practice to put the simplest or most likely-to-fail conditions first - it can make your program run faster! ๐Ÿš€

Remember: In programming, like in real life, sometimes being strict with our checks helps prevent problems later! ๐Ÿ›ก๏ธ

Success Path ๐ŸŽ‰

bookTicket(userTickets, firstName, lastName, email)
            
firstNames := getFirstNames()
fmt.Printf("\n๐ŸŽ‰ Current Bookings: %v\n", firstNames)

You might notice that bookTicket() has a similar syntax to greetUsers(), but with something extra in the parentheses: (userTickets, firstName, lastName, email). What does this syntax tell us about how the function works? And what if we wrote it like getUserInput() or validateUserInput()?

// Function that just does something (no inputs, no returns)
greetUsers()

// Function that takes inputs but doesn't return anything
bookTicket(userTickets, firstName, lastName, email)

// Function that returns values we need to capture
firstName, lastName, email, userTickets := getUserInput()

Different Types of Functions in Our Program ๐ŸŽญ

Let's unpack this together! Think of functions like different types of vending machines - some just give you stuff (like greetUsers()), some expect you to put stuff in (like bookTicket()), and some do both (like getUserInput())! ๐ŸŽฐ

1. Functions That Just Do (like greetUsers)

func greetUsers() {
    fmt.Println("Welcome!")
}
// No parameters needed - like a welcome sign that just shows a message

2. Functions That Need Information (like bookTicket)

func bookTicket(userTickets uint, firstName string, lastName string, email string)
// Needs 4 pieces of info - like a vending machine that needs money AND your selection

3. Functions That Give Back (like getUserInput)

func getUserInput() (string, string, string, uint)
// Returns 4 values - like a vending machine that gives you multiple items

4. Functions That Do Both (like validateUserInput)

func validateUserInput(firstName string, lastName string, email string, userTickets uint) (bool, bool, bool)
// Takes 4 values and returns 3 - like a testing machine that checks your items and gives you results

Let's See Them All Together! ๐ŸŽช

func main() {
    // Just Does
    greetUsers()  // Simple call

    for {
        // Gives Back
        firstName, lastName, email, userTickets := getUserInput()
        // Must store these values to use them!

        // Does Both
        isValidName, isValidEmail, isValidTicketNumber := validateUserInput(firstName, lastName, email, userTickets)
        // Must provide values AND store results!

        if isValidName && isValidEmail && isValidTicketNumber {
            // Needs Information
            bookTicket(userTickets, firstName, lastName, email)
            // Must provide all the required data!
        }
    }
}

Common Mistakes to Avoid! ๐Ÿšซ

1. Forgetting to Catch Returns

// Wrong โŒ
getUserInput()  // Where do the values go?

// Right โœ…
firstName, lastName, email, userTickets := getUserInput()

2. Missing Required Parameters

// Wrong โŒ
bookTicket(2, "John")  // Missing lastName and email!

// Right โœ…
bookTicket(2, "John", "Smith", "john@email.com")

3. Ignoring Returns When You Need Them

// Wrong โŒ
validateUserInput(firstName, lastName, email, userTickets)
if isValidName {  // Where did isValidName come from?

// Right โœ…
isValidName, isValidEmail, isValidTicketNumber := validateUserInput(firstName, lastName, email, userTickets)
if isValidName {  // Now we have the value!

Pro Tips! ๐Ÿ’ก

1. Use Clear Variable Names for Returns

// Not so clear โŒ
a, b, c, d := getUserInput()

// Much better! โœ…
firstName, lastName, email, userTickets := getUserInput()

2. Chain Functions Carefully

// Not possible / Won't compile โŒ
bookTicket(getUserInput())

// Correct way โœ…
firstName, lastName, email, userTickets := getUserInput()
bookTicket(userTickets, firstName, lastName, email)

Now that you've learned about Functions and Types of Functions in Go, you can see how they work together beautifully in a real application. What we've built here isn't just a simple program - it's a practical example of how different functions work together, each with its own specific role:

  • We've got functions that return multiple values (like getUserInput())

  • Functions that take multiple parameters (like validateUserInput())

  • Functions that work with our custom types (like bookTicket() working with our UserData struct)

The cool thing about Go is how it lets us organize our code into these neat, reusable pieces. Think about it - our booking system is like a well-oiled machine where each function is a crucial part:

  • Some parts talk to the user (input/output functions)

  • Some parts check if everything's correct (validation functions)

  • Some parts keep track of our data (booking and tracking functions)

Remember when you were first learning about functions? Now you're using them to build a real booking system! That's pretty awesome progress! ๐Ÿš€

Want to take this further? Let's dive into the most exciting part - handling the actual booking process! ๐ŸŽซ

When someone decides to book tickets for our English course, we need a robust way to process their booking. This is where our bookTicket function comes into play. Think of it as the heart of our booking system - it's where all the magic happens!

First, Let's Create Our Booking Storage ๐Ÿ“ฆ

Before we jump into the functions, we need a place to store our bookings:

// At the top of our file with other global variables
var bookings = make([]UserData, 0)

Imagine you're organizing a big party. You need a list to keep track of all your guests, right? Well, that's exactly what we're creating here! Let me break it down in a way that'll make perfect sense.

Think of this line as creating a special kind of notebook ๐Ÿ“”. But not just any notebook - it's a magical one that can grow and shrink as needed! Here's what's happening:

  • var bookings is like saying "I want to create a new list called 'bookings'"

  • make([]UserData, 0) is like opening a fresh notebook with zero pages, but it's ready to add as many pages as we need

  • []UserData tells Go that each "page" in our notebook will store information about one user (their name, email, and ticket info)

Why do we start with 0 size? Well, it's like starting with an empty guest list. You don't know how many people will book tickets, so you start with a blank list that can grow as people make bookings.

It's different from a regular array (which would be like a notebook with a fixed number of pages) because our slice can grow dynamically. It's perfect for a booking system because:

  • We don't know in advance how many bookings we'll get

  • We can easily add new bookings (just like adding a new page)

  • We can remove bookings if needed (like tearing out a page)

Oh, and here's something really important! ๐Ÿšจ What if we forgot to use make and just wrote:

var bookings []UserData

Ah, let me tell you about a common gotcha that could trip you up! ๐ŸŽฏ

If we just write var bookings []UserData without using make, we'd create what's called a "nil slice". Think of it like having the idea of a notebook in your head, but not actually having the physical notebook in your hands! When you try to add bookings to it (using append), Go will handle it for you behind the scenes, but it's better to be explicit with make. It's like officially opening a new notebook and saying "I'm ready to take bookings!"

Plus, using make has another advantage - it helps other developers understand your intentions right away. They can see that you're purposely creating a slice that's ready to store bookings, rather than just declaring a variable that might be used later. It's like the difference between saying "I might need a notebook someday" versus "I have my notebook ready to go!"

Speaking of adding pages to our notebook, let's talk about append - it's our way of adding new bookings to our list! When you write:

bookings = append(bookings, newBooking)

it's like saying "Hey assistant, please add this new booking to my guest list!" The cool thing about append is that it automatically handles all the behind-the-scenes work. If your notebook runs out of pages, it quietly adds more pages for you. No need to worry about running out of space!

The Booking Function ๐ŸŽฏ

func bookTicket(userTickets uint, firstName string, lastName string, email string) {
    // First, update remaining tickets
    remainingTickets = remainingTickets - userTickets

    // Create a user data structure
    var userData = UserData{
        firstName:       firstName,
        lastName:        lastName,
        email:          email,
        numberOfTickets: userTickets,
    }

    // Add to our bookings slice
    bookings = append(bookings, userData)

    // Show booking confirmation
    fmt.Printf("\n๐ŸŽ‰ Thank you %v %v for booking %v tickets! You'll receive a confirmation email at %v\n",
        firstName, lastName, userTickets, email)
    fmt.Printf("We now have %v tickets remaining for the course\n", remainingTickets)
}

Hey there! Let's break down the bookTicket function line by line. Think of this function as a ticket counter operator processing a sale - it's where all the magic happens!

Function Declaration ๐Ÿ“

func bookTicket(userTickets uint, firstName string, lastName string, email string)

Just like a ticket form, we need specific information:

  • userTickets: How many tickets they want (uint means positive numbers only!)

  • firstName: Their first name

  • lastName: Their last name

  • email: Where to send the confirmation

Step 1: Update Ticket Count โฌ‡๏ธ

remainingTickets = remainingTickets - userTickets

This is like updating our inventory:

  • If we had 30 tickets and someone buys 2

  • 30 - 2 = 28 tickets remaining

  • Think of it as crossing off seats on a seating chart!

Step 2: Create User Record ๐Ÿ“‹

var userData = UserData{
    firstName:       firstName,
    lastName:        lastName,
    email:          email,
    numberOfTickets: userTickets,
}

This is like filling out a customer record card:

  • Create a new UserData struct (like a form)

  • Fill in each field with the information we received

  • The colon-equals (:=) syntax is Go's way of saying "create and fill this form"

Real-world example:

userData = UserData{
    firstName:       "John",
    lastName:        "Smith",
    email:          "john@example.com",
    numberOfTickets: 2,
}

Step 3: Save the Booking โœจ

bookings = append(bookings, userData)

This is like adding a page to our reservation book:

  • bookings is our slice (like a book of records)

  • append adds the new booking to the end

  • It's like saying "Add this customer's info to our guest list"

Think of it as:

Before: bookings = ["Alice", "Bob"]
After append: bookings = ["Alice", "Bob", "John"]

Step 4: Confirmation Message ๐ŸŽ‰

fmt.Printf("\n๐ŸŽ‰ Thank you %v %v for booking %v tickets! You'll receive a confirmation email at %v\n",
    firstName, lastName, userTickets, email)
fmt.Printf("We now have %v tickets remaining for the course\n", remainingTickets)

This is like giving the customer their receipt:

  • First line: Personal confirmation with their details

  • Second line: Public update about remaining tickets

  • The %v are like blanks in a form that Get filled with our values

Example output:

๐ŸŽ‰ Thank you John Smith for booking 2 tickets! You'll receive a confirmation email at john@example.com
We now have 28 tickets remaining for the course

Pro Tips for Working with this Function ๐Ÿ’ก

1. Parameter Ordering

// Keep related data together
bookTicket(userTickets, firstName, lastName, email)
// Rather than
bookTicket(firstName, email, userTickets, lastName) // Confusing!

2. Struct Field Naming

// Clear, matched field names
firstName:       firstName,
// Rather than
first:          firstName, // Inconsistent!

3. Error Checking (A possible improvement)

if remainingTickets < userTickets {
    fmt.Println("Error: Not enough tickets available!")
    return
}

Understanding Return Values: When and Why? ๐Ÿค”

Let's Compare Our Functions

1. getUserInput Function

func getUserInput() (string, string, string, uint) {
    // Code here
    return firstName, lastName, email, userTickets
}

This function NEEDS to return values because:

  • The collected information is needed elsewhere in our program

  • The main function needs these values to continue processing

  • It's like a waiter taking your order and bringing it back to the kitchen

2. validateUserInput Function

func validateUserInput(firstName string, lastName string, email string, userTickets uint) (bool, bool, bool) {
    // Code here
    return isValidName, isValidEmail, isValidTicketNumber
}

This function NEEDS to return values because:

  • The validation results determine what happens next

  • The main function needs to know if the data is valid

  • It's like a bouncer checking IDs and telling you if you can enter

3. bookTicket Function

func bookTicket(userTickets uint, firstName string, lastName string, email string) {
    // Code here
    // No return!
}

This function doesn't need returns because:

  • It performs actions rather than producing new data

  • It modifies our global state (remainingTickets and bookings)

  • It's like a cashier completing a sale - they don't need to "return" anything

  • The confirmation is printed directly to the user

However... We Could Improve It! ๐Ÿš€

Here's how we could make bookTicket better with error handling:

func bookTicket(userTickets uint, firstName string, lastName string, email string) error {
    if remainingTickets < userTickets {
        return fmt.Errorf("sorry, only %v tickets remaining", remainingTickets)
    }
    // Rest of the booking process...
    return nil
}

Then in main:

if err := bookTicket(userTickets, firstName, lastName, email); err != nil {
    fmt.Println("Booking failed:", err)
    continue
}

Why Add Returns? ๐ŸŽฏ

1. Error Handling

  • Return errors when something goes wrong

  • Let the caller decide what to do with errors

  • More flexible and reusable code

2. Testing

  • Easier to test functions that return values

  • Can verify if booking was successful

  • Better for automated testing

3. Future Extensions

  • Easier to add new features

  • Can return booking ID or confirmation number

  • More maintainable code

Remember: The decision to have return values depends on:

  • Does another part of the program need this information?

  • Do we need to know if the operation succeeded?

  • Will we need to test this function?

It's like the difference between:

  • A waiter taking your order (returns info)

  • A chef cooking your meal (performs action)

  • A cashier handling payment (could return receipt/error)

However, for this lesson, let's keep it simple and focus on the basics of User Input and Validation. Instead of implementing complex error handling, we'll continue using our straightforward validation check:

if !isValidTicketNumber {
    fmt.Printf("\nโŒ We only have %v tickets remaining. Please try again.\n", remainingTickets)
}

This way, we can focus on understanding the core concepts before adding more sophisticated error handling patterns. Remember, good programming is often about finding the right balance between simplicity and robustness!

Getting the List of Attendees ๐Ÿ“‹

func getFirstNames() []string {
    // Create a slice to store first names
    firstNames := []string{}

    // Loop through all bookings and get first names
    for _, booking := range bookings {
        firstNames = append(firstNames, booking.firstName)
    }

    return firstNames
}

Let's break down this getFirstNames function line by line. It's a neat little function that helps us see who's coming to our English course!

The Function Signature ๐Ÿ“

func getFirstNames() []string {

Let's decode this:

  • func: We're creating a function

  • getFirstNames: The name of our function (pretty descriptive, right?)

  • []string: This means we're returning a slice of strings (like a dynamic list of names)

Think of []string as preparing a blank guest list that can grow or shrink as needed!

To better understand the []string return type, let's compare it with different return types in Go:

1. Single Return Value:

func getName() string {
    return "John"  // Returns just one string
}

2. Multiple Return Values:

func getNameAndAge() (string, int) {
    return "John", 25  // Returns two separate values
}

3. Our Slice Return Value:

func getFirstNames() []string {
    return ["John", "Jane", "Bob"]  // Returns one slice containing multiple strings
}

Real-world analogy:

  • Single value (string): Like getting one name card

  • Multiple values (string, int): Like getting separate items - a name card AND an age card

  • Slice ([]string): Like getting one stack of name cards bound together

When you use getFirstNames():

// This is ONE variable holding MULTIPLE strings
names := getFirstNames()

// You can access individual names like:
firstPerson := names[0]    // "John"
secondPerson := names[1]   // "Jane"

// You can also see how many names you have:
totalNames := len(names)   // 3

Think of it as the difference between:

  • Getting three separate envelopes (multiple return values)

  • Getting one envelope containing multiple name cards (slice return value)

This is why []string is perfect for our use case - we want to return all names in one neat package! ๐Ÿ“ฆ

Creating an Empty Slice ๐Ÿ†•

firstNames := []string{}

This is like grabbing a blank piece of paper where we'll write our names. In Go:

  • []string{}: Creates an empty slice that can hold strings

  • :=: This is Go's way of saying "create and initialize" in one go

Real-world analogy: It's like opening a new notebook before starting to write names in it!

The Magic Loop โžฟ

for _, booking := range bookings {
    firstNames = append(firstNames, booking.firstName)
}

This is where the magic happens! Let's break it down:

The 'for range' Loop Structure in Go ๐Ÿ”„

In Go, when you write for ... range, you're setting up what we call a "range loop". It's designed specifically for iterating over collections (like arrays, slices, maps, etc.). The structure looks like this:

for index, value := range collection {
    // Do something with index and value
}

The Three Components ๐ŸŽฏ

1. The Index Position (First Variable):

for i, _ := range bookings {  // Using the index
  • This is where Go puts the current position (0, 1, 2, etc.)

  • Like page numbers in a book

  • When we use _, we're saying "Yes, there's an index, but we don't need it"

2. The Value (Second Variable):

for _, booking := range bookings {  // Using just the value
  • This contains the actual item from the collection

  • In our case, each booking is a complete UserData struct

  • Like reading what's written on each page

3. The Collection (After range):

for _, booking := range bookings {  // Iterating over 'bookings'
  • This is what we're looping through

  • Like saying "go through this book"

Remember: The for range loop is like having a really efficient assistant who:

  • Goes through your reservation book page by page

  • Can tell you both the page number (index) and what's on the page (value)

  • But in our case, we only care about what's on the page, not the page number!

The Append Function in Go ๐Ÿ“

firstNames = append(firstNames, booking.firstName)

Think of this like adding a name to a guest list. Let's break it down:

  1. firstNames: Our existing slice (like our current guest list)

  2. append: The function that adds items (like writing a new name)

  3. booking.firstName: The new name we want to add

  4. firstNames =: Saving the updated list back to our variable

Real World Analogy ๐ŸŽฏ

Imagine you're managing a party guest list:

Original list: ["John", "Mary"]
Someone new (booking.firstName = "Bob") wants to join
After append: ["John", "Mary", "Bob"]

It's like saying: "Take my current list, add this new person, and give me back the updated list."

Let's Talk About booking.firstName! ๐ŸŽฏ

Hey there! Let's talk about this interesting bit of code: booking.firstName. It's a great example of how we access data in Go structs!

Remember Our UserData Struct? ๐Ÿ“

type UserData struct {
    firstName       string
    lastName        string
    email          string
    numberOfTickets uint
}

Think of this struct like a form or an ID card - it has different fields for different pieces of information about a person.

How We Access Struct Fields ๐ŸŽฏ

booking.firstName

Let's break this down:

  • booking: This is our struct variable (like holding someone's ID card)

  • . (dot): This is how we say "I want to access a field inside this struct"

  • firstName: This is the specific field we want (like looking at just the name on the ID card)

Real World Example ๐ŸŒŸ

// Let's say we have a booking like this:
booking = UserData{
    firstName:       "John",
    lastName:        "Doe",
    email:          "john@email.com",
    numberOfTickets: 2
}

// When we do:
booking.firstName   // Gets "John"
booking.lastName    // Gets "Doe"
booking.email       // Gets "john@email.com"

It's like looking at different parts of an ID card:

  • Want to see the first name? Look at the firstName field

  • Want to see the email? Look at the email field

Why Use a Dot? ๐Ÿค”

The dot notation (.) is like saying "inside of". So:

booking.firstName
// Means: "get the firstName field inside the booking struct"

Real-world analogy:

  • Think of a filing cabinet (the struct) ๐Ÿ“

  • With different labeled drawers (the fields)

  • The dot is like saying "open the drawer labeled..."

Remember: Using the dot notation with structs is like having a well-organized filing system - you always know exactly where to look for the information you need!

Seeing It All in Action ๐ŸŽฌ

When we call these functions in sequence:

bookTicket(userTickets, firstName, lastName, email)
firstNames := getFirstNames()
fmt.Printf("\n๐ŸŽ‰ Current Bookings: %v\n", firstNames)

It's like this real-world scenario:

1. Someone books tickets (bookTicket):

"Hi, I'm John Smith, booking 2 tickets!"
โ†’ Process payment
โ†’ Update available tickets
โ†’ Add to guest list
โ†’ Send confirmation

2. Then we check the guest list (getFirstNames):

"Let's see who's coming..."
โ†’ Look through all bookings
โ†’ Make a list of first names
โ†’ Show the list

Common Questions ๐Ÿค”

  1. "Why use a slice of UserData instead of separate variables?"

    • Easier to manage multiple bookings

    • More organized code

    • Better for scaling

  2. "Why only get first names in getFirstNames()?"

    • Quick overview of attendees

    • Privacy consideration

    • Cleaner display

Remember: Good booking management is like being an organized event planner - keep track of everything, but make it easy to get the information you need!

Sold Out Check ๐ŸŽซ

if remainingTickets == 0 {
    fmt.Println("\n๐ŸŽซ Sorry, we're all sold out! Come back next time!")
    break
}

This is like checking if we've run out of seats. If we have:

  1. We tell the user we're sold out

  2. We use break to exit our infinite loop (close the shop!)

Error Path โŒ

if !isValidName {
    fmt.Println("\nโŒ Names must be at least 2 characters long")
}
if !isValidEmail {
    fmt.Println("\nโŒ Email must contain @ symbol")
}
if !isValidTicketNumber {
    fmt.Printf("\nโŒ We only have %v tickets remaining. Please try again.\n", remainingTickets)
}

If something's wrong, we tell the user exactly what's wrong! Notice how we check each condition separately? This means if the user makes multiple mistakes, they'll see all the problems at once. It's like a teacher marking a test - better to see all your mistakes at once than one at a time!

Let's Walk Through Our Complete Booking System! ๐ŸŽซ

package main

import (
	"fmt"
	"strings"
)

// Global constants and variables
const conferenceName = "Triangle.Technology English Course"
const conferenceTickets = 30
var remainingTickets uint = 30
var bookings = make([]UserData, 0)

// UserData struct to store booking information
type UserData struct {
	firstName       string
	lastName        string
	email          string
	numberOfTickets uint
}

func main() {
	greetUsers()

	for {
		firstName, lastName, email, userTickets := getUserInput()
		isValidName, isValidEmail, isValidTicketNumber := validateUserInput(firstName, lastName, email, userTickets)

		if isValidName && isValidEmail && isValidTicketNumber {
			bookTicket(userTickets, firstName, lastName, email)
			
			firstNames := getFirstNames()
			fmt.Printf("\n๐ŸŽ‰ Current Bookings: %v\n", firstNames)

			if remainingTickets == 0 {
				fmt.Println("\n๐ŸŽซ Sorry, we're all sold out! Come back next time!")
				break
			}
		} else {
			if !isValidName {
				fmt.Println("\nโŒ Names must be at least 2 characters long")
			}
			if !isValidEmail {
				fmt.Println("\nโŒ Email must contain @ symbol")
			}
			if !isValidTicketNumber {
				fmt.Printf("\nโŒ We only have %v tickets remaining. Please try again.\n", remainingTickets)
			}
		}
	}
}

func greetUsers() {
	fmt.Printf("Welcome to %v Booking System!\n", conferenceName)
	fmt.Printf("We have total of %v tickets and %v are still available\n", conferenceTickets, remainingTickets)
	fmt.Println("Get your tickets here to attend")
}

func getUserInput() (string, string, string, uint) {
	var firstName string
	var lastName string
	var email string
	var userTickets uint

	fmt.Println("\n๐Ÿ‘ค Enter your first name: ")
	fmt.Scan(&firstName)

	fmt.Println("๐Ÿ‘ค Enter your last name: ")
	fmt.Scan(&lastName)

	fmt.Println("๐Ÿ“ง Enter your email address: ")
	fmt.Scan(&email)

	fmt.Println("๐ŸŽŸ๏ธ How many tickets would you like to book? ")
	fmt.Scan(&userTickets)

	return firstName, lastName, email, userTickets
}

func validateUserInput(firstName string, lastName string, email string, userTickets uint) (bool, bool, bool) {
	isValidName := len(firstName) >= 2 && len(lastName) >= 2
	isValidEmail := strings.Contains(email, "@")
	isValidTicketNumber := userTickets > 0 && userTickets <= remainingTickets

	return isValidName, isValidEmail, isValidTicketNumber
}

func bookTicket(userTickets uint, firstName string, lastName string, email string) {
	remainingTickets = remainingTickets - userTickets

	var userData = UserData{
		firstName:       firstName,
		lastName:        lastName,
		email:          email,
		numberOfTickets: userTickets,
	}

	bookings = append(bookings, userData)

	fmt.Printf("\n๐ŸŽ‰ Thank you %v %v for booking %v tickets! You'll receive a confirmation email at %v\n",
		firstName, lastName, userTickets, email)
	fmt.Printf("We now have %v tickets remaining for the course\n", remainingTickets)
}

func getFirstNames() []string {
	firstNames := []string{}
	for _, booking := range bookings {
		firstNames = append(firstNames, booking.firstName)
	}
	return firstNames
}

Hey there! ๐Ÿ‘‹ Now that we've built our entire booking system, let me walk you through how this amazing piece of software works - imagine I'm giving you a tour of a really cool ticket office!

The Setup ๐Ÿ—๏ธ

First, we've built this digital ticket office for Triangle.Technology's English Speaking Course. Think of it as a modern ticket counter, but instead of a person behind the desk, we have a smart computer program! We've got 30 precious tickets available, and we're ready to handle bookings efficiently.

How It All Works ๐ŸŽฏ

When someone wants to book a ticket, here's what happens (and it's pretty exciting!):

1. The Welcome Desk ๐Ÿ‘‹

greetUsers()

Just like a friendly receptionist, our program starts by greeting everyone and announcing how many tickets are available. It's like saying, "Welcome to Triangle.Technology! We've got 30 spots in our awesome English course!"

2. The Never-Ending Service ๐Ÿ”„

for {
   // Our booking magic happens here
}

Like a shop that stays open until they're sold out, our program keeps running, ready to take booking after booking. It only closes when we run out of tickets!

3. The Information Desk ๐Ÿ“

firstName, lastName, email, userTickets := getUserInput()

This is where we collect all the important details. It's like having a friendly staff member ask:

  • "What's your first name?"

  • "And your last name?"

  • "Your email address?"

  • "How many tickets would you like?"

4. The Quality Check โœ…

isValidName, isValidEmail, isValidTicketNumber := validateUserInput()

Before processing any booking, we do some quick checks - like a security guard making sure everything's in order:

  • Is the name long enough? (No single-letter names allowed!)

  • Is the email valid? (Got to have that @ symbol!)

  • Are they asking for a reasonable number of tickets? (Can't book more than we have!)

5. The Magic Moment ๐ŸŽ‰

bookTicket(userTickets, firstName, lastName, email)

If everything checks out, this is where the magic happens! We:

  • Reserve their tickets

  • Record their information

  • Send a nice confirmation message

  • Update our available ticket count

6. The Guest List ๐Ÿ“‹

firstNames := getFirstNames()

After each booking, we show everyone who's coming to the course - it's like having a digital bulletin board showing all our excited participants!

7. The "Sorry, We're Full" Sign ๐Ÿ

if remainingTickets == 0 {
   // Show sold out message and close shop
}

When we hit that magic number of zero tickets remaining, we put up our virtual "Sold Out" sign and close the booking system.

The Beauty of Organization ๐Ÿ’ซ

What makes this system special is how organized everything is:

  • We keep all booking information in a nice, structured format (our UserData struct)

  • We maintain a clean list of all bookings

  • We validate everything before processing

  • We give clear, friendly messages whether things go right or wrong

The User Experience โญ

From a user's perspective, it's like walking into a very efficient, friendly ticket office:

  1. They're greeted warmly

  2. Asked for their information

  3. Their request is processed quickly

  4. They get immediate confirmation

  5. And if something's wrong, they get clear, helpful feedback!

The Smart Parts ๐Ÿง 

Our program is pretty smart too:

  • It never oversells tickets

  • It keeps track of everyone who's booked

  • It makes sure all information is valid

  • It's always polite and helpful in its responses

Remember, this isn't just a program - it's like having a tireless, friendly, efficient ticket seller who's available 24/7, never makes mistakes, and always follows the rules!

Isn't it amazing how a few lines of Go code can create such a helpful system? ๐Ÿš€

Try It Out! ๐Ÿ’ป

Let's test our program with some examples:

Welcome to Triangle.Technology English Course Booking System!
We have 30 tickets available for booking.

๐Ÿ‘ค Enter your first name:
J
๐Ÿ‘ค Enter your last name:
Doe
๐Ÿ“ง Enter your email address:
john@example.com
๐ŸŽŸ๏ธ How many tickets would you like to book?
2

โŒ Names must be at least 2 characters long

Practice Exercise! ๐ŸŽฏ

Try enhancing the validation:

  1. Add check for valid email domain (must end with .com, .org, etc.)

  2. Ensure names don't contain numbers

  3. Add maximum ticket limit per booking

Here's a starter code for email domain validation:

func isValidEmailDomain(email string) bool {
    validDomains := []string{".com", ".org", ".net", ".edu"}
    for _, domain := range validDomains {
        if strings.HasSuffix(email, domain) {
            return true
        }
    }
    return false
}

What's Next? ๐Ÿš€

Now that we can get and validate user input, we're ready to:

  1. Store booking data

  2. Generate booking confirmations

  3. Add a waiting list feature

  4. Implement simple analytics

Ready to move on? Let's keep building! Remember - every great program starts with good input handling!