├── .gitignore ├── LICENSE ├── README.md ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── github.svg ├── go-meta.png ├── go.png ├── go.svg └── vite.svg ├── src ├── App.tsx ├── assets │ ├── fonts │ │ ├── Inter-Bold.otf │ │ ├── Inter-Medium.otf │ │ ├── Inter-Regular.otf │ │ └── Inter-SemiBold.otf │ └── gopher.png ├── components │ ├── About.tsx │ ├── Bookmarks.tsx │ ├── CategoryList.tsx │ ├── Error.tsx │ ├── ExercisePage.tsx │ ├── Exercises.tsx │ ├── Link.tsx │ ├── MarkdownViewer.tsx │ ├── Navbar.tsx │ ├── SEO.tsx │ ├── TopicList.tsx │ └── ui │ │ ├── back-to-top.tsx │ │ ├── bookmark-button.tsx │ │ ├── breadcrumb.tsx │ │ ├── card-hover-effect.tsx │ │ ├── github-button.tsx │ │ └── input-component.tsx ├── data │ ├── advanced-functions │ │ ├── anonymous-functions.md │ │ ├── closures.md │ │ ├── currying.md │ │ ├── defer.md │ │ ├── higher-order-functions.md │ │ ├── pointer-receivers.md │ │ ├── pointers-intro.md │ │ ├── why-higher-order-functions.md │ │ └── wrapping-up.md │ ├── channels │ │ ├── channels-in-go.md │ │ ├── channels-review.md │ │ ├── closing-channels.md │ │ ├── concurrency.md │ │ ├── select-default-case.md │ │ ├── select-statement.md │ │ └── wrapping-up.md │ ├── errors │ │ ├── custom-errors.md │ │ ├── formatting-strings.md │ │ ├── the-error-interface.md │ │ ├── the-errors-package.md │ │ └── wrapping-up.md │ ├── extras │ │ ├── clean-package.md │ │ ├── custom-package.md │ │ ├── proverbs.md │ │ ├── remote-package.md │ │ ├── standard-library.md │ │ └── user-input.md │ ├── functions │ │ ├── benefits-of-named-returns.md │ │ ├── declaration-syntax.md │ │ ├── early-returns.md │ │ ├── intro.md │ │ ├── named-returns.md │ │ ├── return-values.md │ │ └── wrapping-up.md │ ├── interfaces │ │ ├── clean-interfaces.md │ │ ├── interfaces-in-go.md │ │ ├── multiple-interfaces.md │ │ ├── type-assertion.md │ │ └── wrapping-up.md │ ├── intro │ │ ├── basic-syntax.md │ │ ├── compiled-vs-interpreted.md │ │ ├── compiling.md │ │ ├── go-introduction.md │ │ ├── go-is-strongly-typed.md │ │ ├── go-memory-usage.md │ │ ├── requirements.md │ │ └── wrapping-up.md │ ├── loops │ │ ├── continue-and-break.md │ │ ├── intro.md │ │ ├── omitting-conditions.md │ │ ├── operators.md │ │ ├── while-loop.md │ │ └── wrapping-up.md │ ├── maps │ │ ├── key-types.md │ │ ├── maps-in-go.md │ │ ├── maps-review.md │ │ ├── mutating-maps.md │ │ ├── nested-maps.md │ │ └── wrapping-up.md │ ├── mutexes-and-generics │ │ ├── constraints.md │ │ ├── generics-in-go.md │ │ ├── more-on-mutexes.md │ │ ├── mutexes-in-go.md │ │ ├── parametric-constraints.md │ │ ├── rw-mutex-review.md │ │ ├── rw-mutex.md │ │ └── wrapping-up.md │ ├── setting-up-environment │ │ ├── choose-your-tools.md │ │ ├── go-tooling.md │ │ ├── installing-go-tools.md │ │ ├── modules.md │ │ ├── packages.md │ │ ├── wrapping-up.md │ │ └── your-first-go-program.md │ ├── slices │ │ ├── arrays.md │ │ ├── len-and-cap-review.md │ │ ├── make-function.md │ │ ├── slice-gotcha.md │ │ ├── slices-in-go.md │ │ ├── slices-review.md │ │ ├── variadic-functions.md │ │ └── wrapping-up.md │ ├── structs │ │ ├── anonymous-structs.md │ │ ├── embedded-structs.md │ │ ├── nested-structs.md │ │ ├── struct-methods.md │ │ ├── structs-in-go.md │ │ └── wrapping-up.md │ └── variables-and-types │ │ ├── basic-types.md │ │ ├── conditionals.md │ │ ├── constants.md │ │ ├── formatting-strings.md │ │ ├── type-inference.md │ │ ├── type-sizes.md │ │ └── wrapping-up.md ├── hooks │ └── useKey.ts ├── index.css ├── main.tsx ├── utils │ ├── capitalizedWord.ts │ ├── checkRoute.ts │ ├── cn.ts │ └── lists.ts └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Amanuel Chaka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gopher Notes: Your Go-to Spot for Go Programming 2 | 3 | Welcome to the Gopher Notes website! This project is a comprehensive guide to the Go programming language, designed to help developers of all skill levels learn and master the language. The website features a wide range of topics, organized into categories, with each topic covered in depth. 4 | 5 | ## Features 6 | 7 | - **Clear Notes**: Well-organized notes on all the Go stuff, from basic stuff to advanced stuff like concurrency and generics. I kept it real and to the point. 8 | - **Wrapping up sections**: Each topic includes a wrapping up section that provides an overview of the key points discussed, making it easier for users to recap and retain the information. 9 | - **Exercises**: The website offers exercises based on different skill levels, allowing users to practice and reinforce their understanding of the concepts learned. 10 | - **Bookmark feature**: Users can bookmark their favorite topics or pages for quick and easy access. 11 | - **Editing content**: Spot any mistakes? Wanna add something awesome? Go for it! Edit the content directly on GitHub. 12 | - **Dark/Light mode**: The website offers both dark and light mode options, allowing users to choose the mode that suits their preferences and environment. 13 | - **Search functionality**: Users can easily find the information they need using the search functionality, which allows them to search for specific topics or keywords. 14 | 15 | ## Built with React and Love 16 | 17 | This website is made with React, TypeScript, Tailwind CSS, React Router, and React Markdown. 18 | 19 | ## Contributing 20 | 21 | The Gopher Notes website is an open-source project, and contributions from the community are welcome. You can contribute by submitting pull requests on GitHub, adding new topics, fixing bugs, or improving existing content. 22 | 23 | ## Legal Notice 24 | 25 | The contents of this website may be subject to the respective authors and licenses. It is free for personal use but prohibited to publish the contents for commercial use or if it falls outside of the original authors' permission. 26 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 14 | Gopher Notes 15 | 16 | 17 |
18 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gopher-notes", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@uidotdev/usehooks": "^2.4.1", 14 | "clsx": "^2.1.1", 15 | "framer-motion": "^11.3.28", 16 | "lucide-react": "^0.436.0", 17 | "react": "^18.3.1", 18 | "react-dom": "^18.3.1", 19 | "react-helmet-async": "^2.0.5", 20 | "react-markdown": "^9.0.1", 21 | "react-router-dom": "^6.26.1", 22 | "react-syntax-highlighter": "^15.5.0", 23 | "remark-gfm": "^4.0.0", 24 | "tailwind-merge": "^2.5.2" 25 | }, 26 | "devDependencies": { 27 | "@types/react": "^18.3.3", 28 | "@types/react-dom": "^18.3.0", 29 | "@types/react-router-dom": "^5.3.3", 30 | "@types/react-syntax-highlighter": "^15.5.13", 31 | "@typescript-eslint/eslint-plugin": "^7.15.0", 32 | "@typescript-eslint/parser": "^7.15.0", 33 | "@vitejs/plugin-react": "^4.3.1", 34 | "autoprefixer": "^10.4.19", 35 | "eslint": "^8.57.0", 36 | "eslint-plugin-react-hooks": "^4.6.2", 37 | "eslint-plugin-react-refresh": "^0.4.7", 38 | "postcss": "^8.4.40", 39 | "tailwindcss": "^3.4.7", 40 | "typescript": "^5.2.2", 41 | "vite": "^5.3.4" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/go-meta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanuelCh/gopher-notes/2ac2c6a501f409a84481efb0a5a526ba45577dd2/public/go-meta.png -------------------------------------------------------------------------------- /public/go.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanuelCh/gopher-notes/2ac2c6a501f409a84481efb0a5a526ba45577dd2/public/go.png -------------------------------------------------------------------------------- /public/go.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; 2 | import { useLocalStorage } from '@uidotdev/usehooks'; 3 | import SEO from './components/SEO'; 4 | import CategoryList from './components/CategoryList'; 5 | import TopicList from './components/TopicList'; 6 | import MarkdownViewer from './components/MarkdownViewer'; 7 | import Navbar from './components/Navbar'; 8 | import Bookmarks from './components/Bookmarks'; 9 | import Exercises from './components/Exercises'; 10 | import ExercisePage from './components/ExercisePage'; 11 | import About from './components/About'; 12 | import Error from './components/Error'; 13 | 14 | const App = () => { 15 | const [isDark, setIsDark] = useLocalStorage('isDark', true); 16 | const [bookmarkedTopics, setBookmarkedTopics] = useLocalStorage< 17 | { link: string }[] 18 | >('bookmarkedTopics', []); 19 | 20 | const handleDarkToggle = () => { 21 | setIsDark(!isDark); 22 | }; 23 | 24 | return ( 25 |
26 | {isDark ? ( 27 |
28 |
29 |
30 | ) : ( 31 |
32 | )} 33 | 39 | 40 | 44 | 45 | 46 | } 49 | /> 50 | } 53 | /> 54 | 61 | } 62 | /> 63 | 70 | } 71 | /> 72 | } 75 | /> 76 | } 79 | /> 80 | } 83 | /> 84 | } 87 | /> 88 | } 91 | /> 92 | 93 | 94 |
95 | ); 96 | }; 97 | 98 | export default App; 99 | -------------------------------------------------------------------------------- /src/assets/fonts/Inter-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanuelCh/gopher-notes/2ac2c6a501f409a84481efb0a5a526ba45577dd2/src/assets/fonts/Inter-Bold.otf -------------------------------------------------------------------------------- /src/assets/fonts/Inter-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanuelCh/gopher-notes/2ac2c6a501f409a84481efb0a5a526ba45577dd2/src/assets/fonts/Inter-Medium.otf -------------------------------------------------------------------------------- /src/assets/fonts/Inter-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanuelCh/gopher-notes/2ac2c6a501f409a84481efb0a5a526ba45577dd2/src/assets/fonts/Inter-Regular.otf -------------------------------------------------------------------------------- /src/assets/fonts/Inter-SemiBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanuelCh/gopher-notes/2ac2c6a501f409a84481efb0a5a526ba45577dd2/src/assets/fonts/Inter-SemiBold.otf -------------------------------------------------------------------------------- /src/assets/gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanuelCh/gopher-notes/2ac2c6a501f409a84481efb0a5a526ba45577dd2/src/assets/gopher.png -------------------------------------------------------------------------------- /src/components/About.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | import SEO from './SEO'; 3 | import Breadcrumb from './ui/breadcrumb'; 4 | 5 | const About = () => { 6 | return ( 7 |
8 | 14 | 15 |
16 | 17 |
18 | 19 |

About

20 | 21 |
22 |

23 | Gopher notes is an archive of Go notes found online and across open 24 | source materials. It's developed under the intention of helping 25 | aspiring Go developers. If it's your first time using the guide, read 26 | the{' '} 27 | 31 | requirements section 32 | {' '} 33 | and go over the{' '} 34 | 38 | intro part 39 | 40 | . 41 |

42 |

43 | The contents may subject to the respective authors and License. It's 44 | free for personal use but prohibited to publish the contents neither 45 | for commercial use nor if it falls outside of the original authors' 46 | permission. 47 |

48 |

49 | Developed with ♥ by{' '} 50 | 55 | Amanuel Chaka 56 | 57 | . Gopher Notes, 2024 58 |

59 |
60 |
61 | ); 62 | }; 63 | 64 | export default About; 65 | -------------------------------------------------------------------------------- /src/components/Bookmarks.tsx: -------------------------------------------------------------------------------- 1 | import { Link, useNavigate } from 'react-router-dom'; 2 | import SEO from './SEO'; 3 | import Breadcrumb from './ui/breadcrumb'; 4 | 5 | type Props = { 6 | bookmarkedTopics: { link: string }[]; 7 | setBookmarkedTopics: React.Dispatch< 8 | React.SetStateAction< 9 | { 10 | link: string; 11 | }[] 12 | > 13 | >; 14 | }; 15 | 16 | const Bookmarks = ({ bookmarkedTopics, setBookmarkedTopics }: Props) => { 17 | const navigate = useNavigate(); 18 | 19 | // handle clearing bookmark 20 | const handleClearBookmark = () => { 21 | if (!bookmarkedTopics.length) { 22 | navigate('/'); 23 | } 24 | 25 | setBookmarkedTopics([]); 26 | }; 27 | 28 | return ( 29 |
30 | 36 |
37 | 38 |
39 | 40 |
41 |

Bookmarks

42 |

handleClearBookmark()} 45 | > 46 | {bookmarkedTopics.length ? 'Clear' : 'Add bookmarks'} 47 |

48 |
49 | 50 | 84 | {!bookmarkedTopics.length ? ( 85 |

86 | You don't have any bookmarks yet.{' '} 87 |

88 | ) : null} 89 |
90 | ); 91 | }; 92 | 93 | export default Bookmarks; 94 | -------------------------------------------------------------------------------- /src/components/CategoryList.tsx: -------------------------------------------------------------------------------- 1 | import { categories } from '../utils/lists'; 2 | import { HoverEffect } from './ui/card-hover-effect'; 3 | 4 | const CategoryList = () => { 5 | return ( 6 |
7 |
8 | 9 |
10 |
11 | ); 12 | }; 13 | 14 | export default CategoryList; 15 | -------------------------------------------------------------------------------- /src/components/Error.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | 3 | const Error = () => { 4 | return ( 5 |
6 | The requested page doesn't exist. Try searching for topics or navigate 7 | back to{' '} 8 | 12 | home 13 | 14 |
15 | ); 16 | }; 17 | 18 | export default Error; 19 | -------------------------------------------------------------------------------- /src/components/ExercisePage.tsx: -------------------------------------------------------------------------------- 1 | import { useParams } from 'react-router-dom'; 2 | import SEO from './SEO'; 3 | import Breadcrumb from './ui/breadcrumb'; 4 | import { capitalizeWords } from '../utils/capitalizedWord'; 5 | import checkRoute from '../utils/checkRoute'; 6 | import { exercises } from '../utils/lists'; 7 | 8 | const ExercisePage = () => { 9 | const { difficulty } = useParams(); 10 | 11 | checkRoute([difficulty!]); 12 | 13 | // get exercise of the chosen type 14 | const exercise = exercises.filter( 15 | (exercise) => exercise.difficulty.toLowerCase() === difficulty 16 | )[0]; 17 | 18 | return ( 19 |
20 | {' '} 21 | 29 |
30 | 34 |
35 |

{difficulty} Exercises

36 |

{exercise.message}

37 |
    38 | {exercise.exercises.map((levelExercise, idx) => ( 39 |
  1. {levelExercise}
  2. 40 | ))} 41 |
42 |
43 | ); 44 | }; 45 | 46 | export default ExercisePage; 47 | -------------------------------------------------------------------------------- /src/components/Exercises.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | import SEO from './SEO'; 3 | import Breadcrumb from './ui/breadcrumb'; 4 | import { exercises } from '../utils/lists'; 5 | 6 | const Exercises = () => { 7 | return ( 8 |
9 | 15 | 16 |
17 | 18 |
19 | 20 |

Exercises

21 | 22 |
23 | {exercises.map((exercise, idx) => ( 24 | 29 | {exercise.difficulty} 30 | 31 | ))} 32 |
33 |
34 | ); 35 | }; 36 | 37 | export default Exercises; 38 | -------------------------------------------------------------------------------- /src/components/Link.tsx: -------------------------------------------------------------------------------- 1 | // @ts-expect-error 2 | const CustomLink = ({ className, ...props }) => { 3 | return ( 4 | // adds custom "links" class for all links rendered in "MarkdownViewer" component 5 | 9 | ); 10 | }; 11 | 12 | export default CustomLink; 13 | -------------------------------------------------------------------------------- /src/components/SEO.tsx: -------------------------------------------------------------------------------- 1 | import { Helmet } from 'react-helmet-async'; 2 | 3 | type Props = { 4 | title: string | undefined; 5 | description: string; 6 | name: string; 7 | type: string; 8 | }; 9 | 10 | export default function SEO({ title, description, name, type }: Props) { 11 | return ( 12 | 13 | {/* Standard metadata tags */} 14 | {title} 15 | 19 | 20 | {/* Facebook tags */} 21 | 25 | 29 | 33 | 34 | 38 | 39 | {/* Twitter tags */} 40 | 44 | 48 | 52 | 56 | 60 | 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /src/components/TopicList.tsx: -------------------------------------------------------------------------------- 1 | import { Link, useParams } from 'react-router-dom'; 2 | import SEO from './SEO'; 3 | import { categories, topics } from '../utils/lists'; 4 | import Breadcrumb from './ui/breadcrumb'; 5 | import checkRoute from '../utils/checkRoute'; 6 | import { capitalizeWords } from '../utils/capitalizedWord'; 7 | 8 | const TopicList = () => { 9 | const { category } = useParams<{ category: string }>(); 10 | 11 | // check if route is valid 12 | checkRoute(); 13 | 14 | // get topics for the selected category 15 | const topicList = 16 | topics[category?.replaceAll('-', '_') as keyof typeof topics] || []; 17 | 18 | // create a metadata description for the category 19 | const descSEO = categories.filter((prevCategory) => 20 | prevCategory.link.replace('/', '') === category 21 | ? prevCategory.description 22 | : null 23 | )[0].description; 24 | 25 | return ( 26 |
27 | 33 | 34 |
35 | {' '} 36 | 37 |
38 | 39 |

{category?.replaceAll('-', ' ')}

40 |
    41 | {topicList.map((topic) => ( 42 | 47 | {topic.split('-').join(' ')} 48 | 49 | ))} 50 |
51 |
52 | ); 53 | }; 54 | 55 | export default TopicList; 56 | -------------------------------------------------------------------------------- /src/components/ui/back-to-top.tsx: -------------------------------------------------------------------------------- 1 | const BackToTop = () => { 2 | const handleScroll = () => { 3 | window.scrollTo({ 4 | top: 0, 5 | behavior: 'smooth', 6 | }); 7 | }; 8 | 9 | return ( 10 | 24 | ); 25 | }; 26 | 27 | export default BackToTop; 28 | -------------------------------------------------------------------------------- /src/components/ui/bookmark-button.tsx: -------------------------------------------------------------------------------- 1 | type Props = { 2 | text: string; 3 | onBookmark: () => void; 4 | }; 5 | 6 | const BookmarkButton = ({ text, onBookmark }: Props) => { 7 | return ( 8 | 25 | ); 26 | }; 27 | 28 | export default BookmarkButton; 29 | -------------------------------------------------------------------------------- /src/components/ui/breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | 3 | type Props = { 4 | category: string | undefined; 5 | topic?: string; 6 | }; 7 | 8 | const Breadcrumb = ({ category, topic }: Props) => { 9 | return ( 10 | 83 | ); 84 | }; 85 | 86 | export default Breadcrumb; 87 | -------------------------------------------------------------------------------- /src/components/ui/github-button.tsx: -------------------------------------------------------------------------------- 1 | type Props = { 2 | to: string; 3 | }; 4 | 5 | const GitHubButton = ({ to }: Props) => { 6 | return ( 7 |
12 | 13 |
14 | 18 | 19 | 20 | Edit on GitHub 21 |
22 |
23 | ); 24 | }; 25 | 26 | export default GitHubButton; 27 | -------------------------------------------------------------------------------- /src/data/advanced-functions/anonymous-functions.md: -------------------------------------------------------------------------------- 1 | # Anonymous Functions 2 | 3 | Anonymous functions are form of functions in that they have *no name*. Anonymous functions are useful when defining a function that will only be used once or to create a quick [closure](https://en.wikipedia.org/wiki/Closure_(computer_programming)). 4 | 5 | ```go 6 | // doMath accepts a function that converts one int into another 7 | // and a slice of ints. It returns a slice of ints that have been 8 | // converted by the passed in function. 9 | func doMath(f func(int) int, nums []int) []int { 10 | var results []int 11 | for _, n := range nums { 12 | results = append(results, f(n)) 13 | } 14 | return results 15 | } 16 | 17 | func main() { 18 | nums := []int{1, 2, 3, 4, 5} 19 | 20 | // Here we define an anonymous function that doubles an int 21 | // and pass it to doMath 22 | allNumsDoubled := doMath(func(x int) int { 23 | return x + x 24 | }, nums) 25 | 26 | fmt.Println(allNumsDoubled) 27 | // prints: 28 | // [2 4 6 8 10] 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /src/data/advanced-functions/closures.md: -------------------------------------------------------------------------------- 1 | # Closures 2 | 3 | A closure is a function that references variables from outside its own function body. The function may access and *assign* to the referenced variables. 4 | 5 | In this example, the `concatter()` function returns a function that has reference to an *enclosed* `doc` value. Each successive call to `harryPotterAggregator` mutates that same `doc` variable. 6 | 7 | ```go 8 | func concatter() func(string) string { 9 | doc := "" 10 | return func(word string) string { 11 | doc += word + " " 12 | return doc 13 | } 14 | } 15 | 16 | func main() { 17 | harryPotterAggregator := concatter() 18 | harryPotterAggregator("Mr.") 19 | harryPotterAggregator("and") 20 | harryPotterAggregator("Mrs.") 21 | harryPotterAggregator("Dursley") 22 | harryPotterAggregator("of") 23 | harryPotterAggregator("number") 24 | harryPotterAggregator("four,") 25 | harryPotterAggregator("Privet") 26 | 27 | fmt.Println(harryPotterAggregator("Drive")) 28 | // Mr. and Mrs. Dursley of number four, Privet Drive 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /src/data/advanced-functions/currying.md: -------------------------------------------------------------------------------- 1 | # Currying 2 | 3 | Function currying is the practice of writing a function that takes a function (or functions) as input, and returns a new function. 4 | 5 | For example: 6 | 7 | ```go 8 | func main() { 9 | squareFunc := selfMath(multiply) 10 | doubleFunc := selfMath(add) 11 | 12 | fmt.Println(squareFunc(5)) 13 | // prints 25 14 | 15 | fmt.Println(doubleFunc(5)) 16 | // prints 10 17 | } 18 | 19 | func multiply(x, y int) int { 20 | return x * y 21 | } 22 | 23 | func add(x, y int) int { 24 | return x + y 25 | } 26 | 27 | func selfMath(mathFunc func(int, int) int) func (int) int { 28 | return func(x int) int { 29 | return mathFunc(x, x) 30 | } 31 | } 32 | ``` 33 | 34 | In the example above, the `selfMath` function takes in a function as its parameter, and returns a function that itself returns the value of running that input function on its parameter. 35 | -------------------------------------------------------------------------------- /src/data/advanced-functions/defer.md: -------------------------------------------------------------------------------- 1 | # Defer 2 | 3 | The `defer` keyword is a fairly unique feature of Go. It allows a function to be executed automatically *just before* its enclosing function returns. 4 | 5 | The deferred call's arguments are evaluated immediately, but the function call is not executed until the surrounding function returns. 6 | 7 | Deferred functions are typically used to close database connections, file handlers and the like. 8 | 9 | For example: 10 | 11 | ```go 12 | // CopyFile copies a file from srcName to dstName on the local filesystem. 13 | func CopyFile(dstName, srcName string) (written int64, err error) { 14 | 15 | // Open the source file 16 | src, err := os.Open(srcName) 17 | if err != nil { 18 | return 19 | } 20 | // Close the source file when the CopyFile function returns 21 | defer src.Close() 22 | 23 | // Create the destination file 24 | dst, err := os.Create(dstName) 25 | if err != nil { 26 | return 27 | } 28 | // Close the destination file when the CopyFile function returns 29 | defer dst.Close() 30 | 31 | return io.Copy(dst, src) 32 | } 33 | ``` 34 | 35 | In the above example, the `src.Close()` function is not called until after the `CopyFile` function was called but immediately before the `CopyFile` function returns. 36 | 37 | Defer is a great way to **make sure** that something happens at the end of a function, even if there are multiple return statements. -------------------------------------------------------------------------------- /src/data/advanced-functions/higher-order-functions.md: -------------------------------------------------------------------------------- 1 | # First Class and Higher Order Functions 2 | 3 | A programming language is said to have "first-class functions" when functions in that language are treated like any other variable. For example, in such a language, a function can be passed as an argument to other functions, can be returned by another function and can be assigned as a value to a variable. 4 | 5 | A function that returns a function or accepts a function as input is called a Higher-Order Function. 6 | 7 | Go supports [first-class](https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function) and higher-order functions. Another way to think of this is that a function is just another type -- just like `int`s and `string`s and `bool`s. 8 | 9 | For example, to accept a function as a parameter: 10 | 11 | ```go 12 | func add(x, y int) int { 13 | return x + y 14 | } 15 | 16 | func mul(x, y int) int { 17 | return x * y 18 | } 19 | 20 | // aggregate applies the given math function to the first 3 inputs 21 | func aggregate(a, b, c int, arithmetic func(int, int) int) int { 22 | return arithmetic(arithmetic(a, b), c) 23 | } 24 | 25 | func main(){ 26 | fmt.Println(aggregate(2,3,4, add)) 27 | // prints 9 28 | fmt.Println(aggregate(2,3,4, mul)) 29 | // prints 24 30 | } 31 | ``` 32 | -------------------------------------------------------------------------------- /src/data/advanced-functions/pointer-receivers.md: -------------------------------------------------------------------------------- 1 | # Pointer Receivers 2 | 3 | A receiver type on a method can be a pointer. 4 | 5 | Methods with pointer receivers can modify the value to which the receiver points. Since methods often need to modify their receiver, pointer receivers are *more common* than value receivers. 6 | 7 | ## Pointer Receiver 8 | 9 | ```go 10 | type car struct { 11 | color string 12 | } 13 | 14 | func (c *car) setColor(color string) { 15 | c.color = color 16 | } 17 | 18 | func main() { 19 | c := car{ 20 | color: "white", 21 | } 22 | c.setColor("blue") 23 | fmt.Println(c.color) 24 | // prints "blue" 25 | } 26 | ``` 27 | 28 | ## Non-pointer Receiver 29 | 30 | ```go 31 | type car struct { 32 | color string 33 | } 34 | 35 | func (c car) setColor(color string) { 36 | c.color = color 37 | } 38 | 39 | func main() { 40 | c := car{ 41 | color: "white", 42 | } 43 | c.setColor("blue") 44 | fmt.Println(c.color) 45 | // prints "white" 46 | } 47 | ``` 48 | 49 | ## Pointer Receiver Code 50 | 51 | Methods with pointer receivers don't require that a pointer is used to call the method. The pointer will automatically be derived from the value. 52 | 53 | ```go 54 | type circle struct { 55 | x int 56 | y int 57 | radius int 58 | } 59 | 60 | func (c *circle) grow(){ 61 | c.radius *= 2 62 | } 63 | 64 | func main(){ 65 | c := circle{ 66 | x: 1, 67 | y: 2, 68 | radius: 4, 69 | } 70 | 71 | // notice c is not a pointer in the calling function 72 | // but the method still gains access to a pointer to c 73 | c.grow() 74 | fmt.Println(c.radius) 75 | // prints 8 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /src/data/advanced-functions/pointers-intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to Pointers 2 | 3 | As we know, a variable is a named location in memory that stores a value. We can manipulate the value of a variable by assigning a new value to it or by performing operations on it. When we assign a value to a variable, we are storing that value in a specific location in memory. 4 | 5 | ```go 6 | x := 42 7 | // "x" is the name of a location in memory. That location is storing the integer value of 42 8 | ``` 9 | 10 | ## A Pointer is a Variable 11 | 12 | A pointer is a variable that stores the *memory address* of another variable. This means that a pointer "points to" the *location* of where the data is stored *NOT* the actual data itself. 13 | 14 | The `*` syntax defines a pointer: 15 | 16 | ```go 17 | var p *int 18 | ``` 19 | 20 | The `&` operator generates a pointer to its operand. 21 | 22 | ```go 23 | myString := "hello" 24 | myStringPtr = &myString 25 | ``` 26 | 27 | ## Why are Pointers Useful? 28 | 29 | Pointers allow us to manipulate data in memory directly, without making copies or duplicating data. This can make programs more efficient and allow us to do things that would be difficult or impossible without them. 30 | 31 | > That said, pointers can be *very* dangerous. It's generally a better idea to have your functions accept non-pointers and return new values rather than mutating pointer inputs. 32 | 33 | ## Nil Pointers 34 | 35 | Pointers can be very dangerous. If a pointer points to nothing (the zero value of the pointer type) then dereferencing it will cause a runtime error (a [panic](https://gobyexample.com/panic)) that crashes the program. Generally speaking, whenever you're dealing with pointers you should check if it's `nil` before trying to dereference it. -------------------------------------------------------------------------------- /src/data/advanced-functions/why-higher-order-functions.md: -------------------------------------------------------------------------------- 1 | # Why First Class and Higher Order Functions? 2 | 3 | At first, it may seem like dynamically creating functions and passing them around as variables adds unnecessary complexity. Most of the time you would be right. There are cases however when functions as values make a lot of sense. Some of these include: 4 | 5 | - [HTTP API](https://en.wikipedia.org/wiki/Web_API) handlers 6 | - [Pub/Sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) handlers 7 | - Onclick callbacks 8 | 9 | Any time you need to run custom code at *a time in the future*, functions as values might make sense. 10 | 11 | ## Definition: First-class Functions 12 | 13 | A first-class function is a function that can be treated like any other value. Go supports first-class functions. A function's type is dependent on the types of its parameters and return values. For example, these are different function types: 14 | 15 | ```go 16 | func() int 17 | ``` 18 | 19 | ```go 20 | func(string) int 21 | ``` 22 | 23 | ## Definition: Higher-Order Functions 24 | 25 | A higher-order function is a function that takes a function as an argument or returns a function as a return value. Go supports higher-order functions. For example, this function takes a function as an argument: 26 | 27 | ```go 28 | func aggregate(a, b, c int, arithmetic func(int, int) int) int 29 | ``` 30 | -------------------------------------------------------------------------------- /src/data/advanced-functions/wrapping-up.md: -------------------------------------------------------------------------------- 1 | # Wrapping Up 2 | 3 | ## Advanced Function Techniques in Go 4 | 5 | Go, while known for its simplicity, offers powerful capabilities for manipulating and leveraging functions in advanced ways. 6 | 7 | ### High-Order Functions: Taking Functions as Arguments 8 | 9 | High-order functions are functions that can accept other functions as arguments or return functions as results. This allows you to create reusable and flexible code structures. 10 | 11 | ```go 12 | func apply(f func(int) int, x int) int { 13 | return f(x) 14 | } 15 | 16 | func square(x int) int { 17 | return x * x 18 | } 19 | 20 | result := apply(square, 5) // Pass the "square" function as an argument 21 | fmt.Println(result) // Output: 25 22 | ``` 23 | 24 | ### First-Class Functions: Treating Functions as Data 25 | 26 | In Go, functions are first-class citizens, meaning they can be treated like any other data type. You can assign functions to variables, pass them as arguments, and return them from other functions. 27 | 28 | ```go 29 | func getGreeting(name string) func() string { 30 | return func() string { 31 | return "Hello, " + name + "!" 32 | } 33 | } 34 | 35 | greeting := getGreeting("Alice") 36 | message := greeting() // Call the returned function 37 | fmt.Println(message) // Output: Hello, Alice! 38 | ``` 39 | 40 | ### Currying: Partially Applying Functions 41 | 42 | Currying is a technique that involves transforming a function that takes multiple arguments into a series of nested functions, each taking a single argument. 43 | 44 | ```go 45 | func add(x int) func(int) int { 46 | return func(y int) int { 47 | return x + y 48 | } 49 | } 50 | 51 | add5 := add(5) // Partially apply the "add" function 52 | result := add5(10) // Apply the remaining argument 53 | fmt.Println(result) // Output: 15 54 | ``` 55 | 56 | ### `defer` Statements: Scheduling Code for Later 57 | 58 | The `defer` statement schedules a function call to be executed after the surrounding function returns. This is useful for tasks like closing files, releasing resources, or performing cleanup actions. 59 | 60 | ```go 61 | func readFile(filename string) { 62 | file, err := os.Open(filename) 63 | if err != nil { 64 | // Handle the error 65 | } 66 | defer file.Close() // Close the file when the function returns 67 | // ... process the file content ... 68 | } 69 | ``` 70 | 71 | ### Closures: Capturing Variables from the Enclosing Scope 72 | 73 | Closures allow functions to access variables from their surrounding scope, even after the outer function has returned. This enables you to create functions with state. 74 | 75 | ```go 76 | func makeCounter() func() int { 77 | count := 0 78 | return func() int { 79 | count++ 80 | return count 81 | } 82 | } 83 | 84 | counter := makeCounter() 85 | fmt.Println(counter()) // Output: 1 86 | fmt.Println(counter()) // Output: 2 87 | ``` 88 | 89 | ### Anonymous Functions: Functions Without Names 90 | 91 | Anonymous functions are functions that are not declared with a name. They are often used as arguments to other functions or returned as results. 92 | 93 | ```go 94 | func apply(f func(int) int, x int) int { 95 | return f(x) 96 | } 97 | 98 | result := apply(func(x int) int { return x * 2 }, 5) // Anonymous function as argument 99 | fmt.Println(result) // Output: 10 100 | ``` 101 | 102 | ### Pointers: Referencing Memory Addresses 103 | 104 | Pointers store the memory addresses of other variables. This allows you to modify the values of variables directly, without copying them. 105 | 106 | ```go 107 | var x int = 10 108 | var p *int = &x // "p" points to the memory address of "x" 109 | *p = 20 // Modifies the value of "x" through the pointer 110 | fmt.Println(x) // Output: 20 111 | ``` 112 | 113 | ### Pointer Receivers: Modifying Values Directly 114 | 115 | Methods can have pointer receivers, allowing them to modify the values of the underlying object directly, without creating a copy. 116 | 117 | ```go 118 | type Counter struct { 119 | count int 120 | } 121 | 122 | func (c *Counter) Increment() { 123 | c.count++ 124 | } 125 | 126 | counter := Counter{0} 127 | counter.Increment() // Increment the counter's value 128 | fmt.Println(counter.count) // Output: 1 129 | ``` 130 | 131 | You've explored a range of advanced function techniques in Go! 132 | -------------------------------------------------------------------------------- /src/data/channels/channels-in-go.md: -------------------------------------------------------------------------------- 1 | # Channels 2 | 3 | Channels are a typed, thread-safe queue. Channels allow different goroutines to communicate with each other. 4 | 5 | ## Create a channel 6 | 7 | Like maps and slices, channels must be created *before* use. They also use the same `make` keyword: 8 | 9 | ```go 10 | ch := make(chan int) 11 | ``` 12 | 13 | ## Send data to a channel 14 | 15 | ```go 16 | ch <- 69 17 | ``` 18 | 19 | The `<-` operator is called the *channel operator*. Data flows in the direction of the arrow. This operation will *block* until another goroutine is ready to receive the value. 20 | 21 | ## Receive data from a channel 22 | 23 | ```go 24 | v := <-ch 25 | ``` 26 | 27 | This reads and removes a value from the channel and saves it into the variable `v`. This operation will *block* until there is a value in the channel to be read. 28 | 29 | ## Blocking and deadlocks 30 | 31 | A [deadlock](https://yourbasic.org/golang/detect-deadlock/#:~:text=yourbasic.org%2Fgolang,look%20at%20this%20simple%20example.) is when a group of goroutines are all blocking so none of them can continue. This is a common bug that you need to watch out for in concurrent programming. 32 | 33 | > Empty structs are often used as `tokens` in Go programs. In this context, a token is a [unary](https://en.wikipedia.org/wiki/Unary_operation) value. In other words, we don't care *what* is passed through the channel. We care *when* and *if* it is passed. 34 | 35 | We can block and wait until *something* is sent on a channel using the following syntax 36 | 37 | ```go 38 | <-ch 39 | ``` 40 | 41 | This will block until it pops a single item off the channel, then continue, discarding the item. 42 | -------------------------------------------------------------------------------- /src/data/channels/channels-review.md: -------------------------------------------------------------------------------- 1 | # Channels Review 2 | 3 | Here are a few extra things you should understand about channels from [Dave Cheney's awesome article](https://dave.cheney.net/2014/03/19/channel-axioms). 4 | 5 | ### A send to a nil channel blocks forever 6 | 7 | ```go 8 | var c chan string // c is nil 9 | c <- "let's get started" // blocks 10 | ``` 11 | 12 | ### A receive from a nil channel blocks forever 13 | 14 | ```go 15 | var c chan string // c is nil 16 | fmt.Println(<-c) // blocks 17 | ``` 18 | 19 | ### A send to a closed channel panics 20 | 21 | ```go 22 | var c = make(chan int, 100) 23 | close(c) 24 | c <- 1 // panic: send on closed channel 25 | ``` 26 | 27 | ### A receive from a closed channel returns the zero value immediately 28 | 29 | ```go 30 | var c = make(chan int, 100) 31 | close(c) 32 | fmt.Println(<-c) // 0 33 | ``` 34 | -------------------------------------------------------------------------------- /src/data/channels/closing-channels.md: -------------------------------------------------------------------------------- 1 | # Closing channels in Go 2 | 3 | Channels can be explicitly closed by a *sender*: 4 | 5 | ```go 6 | ch := make(chan int) 7 | 8 | // do some stuff with the channel 9 | 10 | close(ch) 11 | ``` 12 | 13 | ## Checking if a channel is closed 14 | 15 | Similar to the `ok` value when accessing data in a `map`, receivers can check the `ok` value when receiving from a channel to test if a channel was closed. 16 | 17 | ```go 18 | v, ok := <-ch 19 | ``` 20 | 21 | ok is `false` if the channel is empty and closed. 22 | 23 | ## Don't send on a closed channel 24 | 25 | Sending on a closed channel will cause a panic. A panic on the main goroutine will cause the entire program to crash, and a panic in any other goroutine will cause *that goroutine* to crash. 26 | 27 | Closing isn't necessary. There's nothing wrong with leaving channels open, they'll still be garbage collected if they're unused. You should close channels to indicate explicitly to a receiver that nothing else is going to come across. 28 | 29 | ## Buffered Channels 30 | 31 | Channels can *optionally* be buffered. 32 | 33 | ### Creating a channel with a buffer 34 | 35 | You can provide a buffer length as the second argument to `make()` to create a buffered channel: 36 | 37 | ```go 38 | ch := make(chan int, 100) 39 | ``` 40 | 41 | Sending on a buffered channel only blocks when the buffer is *full*. 42 | 43 | Receiving blocks only when the buffer is *empty*. 44 | -------------------------------------------------------------------------------- /src/data/channels/concurrency.md: -------------------------------------------------------------------------------- 1 | # Concurrency 2 | 3 | ## What is concurrency? 4 | 5 | Concurrency is the ability to perform multiple tasks at the same time. Typically, our code is executed one line at a time, one after the other. This is called *sequential execution* or *synchronous execution*. 6 | 7 | ![concurrency](https://i.imgur.com/1pQKFgw.png) 8 | 9 | If the computer we're running our code on has multiple cores, we can even execute multiple tasks at *exactly* the same time. If we're running on a single core, a single code executes code at *almost* the same time by switching between tasks very quickly. Either way, the code we write looks the same in Go and takes advantage of whatever resources are available. 10 | 11 | ## How does concurrency work in Go? 12 | 13 | Go was designed to be concurrent, which is a trait *fairly* unique to Go. It excels at performing many tasks simultaneously safely using a simple syntax. 14 | 15 | There isn't a popular programming language in existence where spawning concurrent execution is quite as elegant, at least in my opinion. 16 | 17 | Concurrency is as simple as using the `go` keyword when calling a function: 18 | 19 | ```go 20 | go doSomething() 21 | ``` 22 | 23 | In the example above, `doSomething()` will be executed concurrently with the rest of the code in the function. The `go` keyword is used to spawn a new *[goroutine](https://gobyexample.com/goroutines)*. 24 | -------------------------------------------------------------------------------- /src/data/channels/select-default-case.md: -------------------------------------------------------------------------------- 1 | # Select Default Case 2 | 3 | The `default` case in a `select` statement executes *immediately* if no other channel has a value ready. A `default` case stops the `select` statement from blocking. 4 | 5 | ```go 6 | select { 7 | case v := <-ch: 8 | // use v 9 | default: 10 | // receiving from ch would block 11 | // so do something else 12 | } 13 | ``` 14 | 15 | ## Tickers 16 | 17 | - [time.Tick()](https://golang.org/pkg/time/#Tick) - is a standard library function that returns a channel that sends a value on a given interval. 18 | - [time.After()](https://golang.org/pkg/time/#After) - sends a value once after the duration has passed. 19 | - [time.Sleep()](https://golang.org/pkg/time/#Sleep) - blocks the current goroutine for the specified amount of time. 20 | 21 | ## Read-only Channels 22 | 23 | A channel can be marked as read-only by casting it from a `chan` to a `<-chan` type. For example: 24 | 25 | ```go 26 | func main(){ 27 | ch := make(chan int) 28 | readCh(ch) 29 | } 30 | 31 | func readCh(ch <-chan int) { 32 | // ch can only be read from 33 | // in this function 34 | } 35 | ``` 36 | 37 | ## Write-only Channels 38 | 39 | The same goes for write-only channels, but the arrow's position moves. 40 | 41 | ```go 42 | func writeCh(ch chan<- int) { 43 | // ch can only be written to 44 | // in this function 45 | } 46 | ``` 47 | -------------------------------------------------------------------------------- /src/data/channels/select-statement.md: -------------------------------------------------------------------------------- 1 | # Select 2 | 3 | Sometimes we have a single goroutine listening to multiple channels and want to process data in the order it comes through each channel. 4 | 5 | A `select` statement is used to listen to multiple channels at the same time. It is similar to a `switch` statement but for channels. 6 | 7 | ```go 8 | select { 9 | case i, ok := <- chInts: 10 | fmt.Println(i) 11 | case s, ok := <- chStrings: 12 | fmt.Println(s) 13 | } 14 | ``` 15 | 16 | The first channel with a value ready to be received will fire and its body will execute. If multiple channels are ready at the same time one is chosen randomly. The `ok` variable in the example above refers to whether or not the channel has been closed by the sender yet. 17 | 18 | ## Range 19 | 20 | Similar to slices and maps, channels can be ranged over. 21 | 22 | ```go 23 | for item := range ch { 24 | // item is the next value received from the channel 25 | } 26 | ``` 27 | 28 | This example will receive values over the channel (blocking at each iteration if nothing new is there) and will exit only when the channel is closed. 29 | -------------------------------------------------------------------------------- /src/data/channels/wrapping-up.md: -------------------------------------------------------------------------------- 1 | # Wrapping Up 2 | 3 | ## Concurrency 4 | 5 | Concurrency is the ability to handle multiple tasks seemingly simultaneously and a powerful paradigm for writing efficient and responsive programs. Go, with its built-in support for concurrency, makes it effortless to harness this power through its elegant channel mechanism. 6 | 7 | ### Concurrency in Go: Embracing Parallelism 8 | 9 | Go's concurrency model is based on the concept of goroutines, lightweight threads managed by the Go runtime, and channels, typed communication pipelines for goroutines. This allows you to run multiple functions concurrently, exchanging data between them seamlessly. 10 | 11 | ### Channels: Typed Communication Pipes 12 | 13 | Channels are typed conduits for passing data between goroutines. They ensure safe and synchronized communication, preventing race conditions. 14 | 15 | ```go 16 | ch := make(chan int) // Create a channel of integers 17 | ``` 18 | 19 | ```go 20 | // Sending Data 21 | ch <- 5 // Send the value 5 to the channel 22 | 23 | // Receiving Data 24 | value := <-ch // Receive a value from the channel 25 | ``` 26 | 27 | ### The `select` Keyword: Multi-Way Channel Communication 28 | 29 | The `select` statement provides a powerful way to handle multiple channel operations concurrently. It allows you to choose the first channel operation that becomes ready, elegantly handling multiple communication paths. 30 | 31 | ```go 32 | select { 33 | case value := <-ch1: 34 | fmt.Println("Received from ch1:", value) 35 | case ch2 <- 10: 36 | fmt.Println("Sent to ch2: 10") 37 | default: 38 | fmt.Println("No channels ready") 39 | } 40 | ``` 41 | 42 | ### Channel Directions: Specifying Input and Output 43 | 44 | You can specify the direction of a channel using the `<-` operator in its declaration: 45 | 46 | Send-only channel: 47 | 48 | ```go 49 | ch := make(chan<- int) // Can only send data 50 | ``` 51 | 52 | Receive-only channel: 53 | 54 | ```go 55 | ch := make(<-chan int) // Can only receive data 56 | ``` 57 | 58 | ### Unbuffered Channels: Synchronous Communication 59 | 60 | Unbuffered channels require a sender and receiver to be ready simultaneously for data to be transferred. This provides a form of synchronization. 61 | 62 | ```go 63 | ch := make(chan int) // Unbuffered channel 64 | ch <- 5 // Sender waits until a receiver is ready 65 | value := <-ch // Receiver waits until a sender is ready 66 | ``` 67 | 68 | ### Buffered Channels: Asynchronous Communication 69 | 70 | Buffered channels allow a certain number of values to be queued before a receiver is ready. This enables more asynchronous communication. 71 | 72 | ```go 73 | ch := make(chan int, 2) // Buffered channel with capacity 2 74 | ch <- 5 // Send a value without waiting 75 | ch <- 10 // Send another value without waiting 76 | value := <-ch // Receive the first value 77 | ``` 78 | 79 | ### Closing Channels: Signaling Completion 80 | 81 | The `close` function signals that a channel is no longer being used. Receivers can detect a closed channel and handle the situation accordingly. 82 | 83 | ```go 84 | close(ch) // Close the channel 85 | value, ok := <-ch // Receive from the closed channel 86 | if ok { 87 | // Process the value 88 | } else { 89 | // Handle the case where the channel is closed 90 | } 91 | ``` 92 | -------------------------------------------------------------------------------- /src/data/errors/custom-errors.md: -------------------------------------------------------------------------------- 1 | # Custom Errors 2 | 3 | Because errors are just interfaces, you can build your own custom types that implement the `error` interface. Here's an example of a `userError` struct that implements the `error` interface: 4 | 5 | ```go 6 | type userError struct { 7 | name string 8 | } 9 | 10 | func (e userError) Error() string { 11 | return fmt.Sprintf("%v has a problem with their account", e.name) 12 | } 13 | ``` 14 | 15 | It can then be used as an error: 16 | 17 | ```go 18 | func sendSMS(msg, userName string) error { 19 | if !canSendToUser(userName) { 20 | return userError{name: userName} 21 | } 22 | ... 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /src/data/errors/formatting-strings.md: -------------------------------------------------------------------------------- 1 | # Formatting Strings Review 2 | 3 | A convenient way to format strings in Go is by using the standard library's [fmt.Sprintf()](https://pkg.go.dev/fmt#example-Sprintf) function. It's a string interpolation function, similar to JavaScript's built-in template literals. The `%v` substring uses the type's default formatting, which is often what you want. 4 | 5 | ## Default Values 6 | 7 | ```go 8 | const name = "Kim" 9 | const age = 22 10 | s := fmt.Sprintf("%v is %v years old.", name, age) 11 | // s = "Kim is 22 years old." 12 | ``` 13 | 14 | The equivalent JavaScript code: 15 | 16 | ```js 17 | const name = 'Kim' 18 | const age = 22 19 | s = `${name} is ${age} years old.` 20 | // s = "Kim is 22 years old." 21 | ``` 22 | 23 | ## Rounding floats 24 | 25 | ```go 26 | fmt.Printf("I am %f years old", 10.523) 27 | // I am 10.523000 years old 28 | 29 | // The ".2" rounds the number to 2 decimal places 30 | fmt.Printf("I am %.2f years old", 10.523) 31 | // I am 10.53 years old 32 | ``` 33 | -------------------------------------------------------------------------------- /src/data/errors/the-error-interface.md: -------------------------------------------------------------------------------- 1 | # The Error Interface 2 | 3 | Go programs express errors with `error` values. An Error is any type that implements the simple built-in [error interface](https://blog.golang.org/error-handling-and-go): 4 | 5 | ```go 6 | type error interface { 7 | Error() string 8 | } 9 | ``` 10 | 11 | When something can go wrong in a function, that function should return an `error` as its last return value. Any code that calls a function that can return an `error` should handle errors by testing whether the error is `nil`. 12 | 13 | ```go 14 | // Atoi converts a stringified number to an interger 15 | i, err := strconv.Atoi("42b") 16 | if err != nil { 17 | fmt.Println("couldn't convert:", err) 18 | // because "42b" isn't a valid integer, we print: couldn't convert: strconv.Atoi: parsing "42b": invalid syntax 19 | // Note: 20 | // 'parsing "42b": invalid syntax' is returned by the .Error() method 21 | return 22 | } 23 | // if we get here, then i was converted successfully 24 | ``` 25 | 26 | A `nil` error denotes success; a non-nil error denotes failure. 27 | 28 | > When you return a non-nil error in Go, it's conventional to return the "zero" values of all other return values. 29 | -------------------------------------------------------------------------------- /src/data/errors/the-errors-package.md: -------------------------------------------------------------------------------- 1 | # The Errors Package 2 | 3 | The Go standard library provides an "errors" package that makes it easy to deal with errors. Read the godoc for the [errors.New()](https://pkg.go.dev/errors#New) function, but here's a simple example: 4 | 5 | ```go 6 | var err error = errors.New("something went wrong") 7 | ``` 8 | 9 | ### Remember 10 | 11 | Go programs express errors with `error` values. Error-values are any type that implements the simple built-in [error interface](https://blog.golang.org/error-handling-and-go). Keep in mind that the way Go handles errors is fairly unique. Most languages treat errors as something special and different. For example, Python raises exception types and JavaScript throws and catches errors. In Go, an `error` is just another value that we handle like any other value - however, we want! There aren't any special keywords for dealing with them. 12 | -------------------------------------------------------------------------------- /src/data/errors/wrapping-up.md: -------------------------------------------------------------------------------- 1 | # Wrapping Up 2 | 3 | ## Handling Errors Gracefully 4 | 5 | Errors are an inevitable part of programming. Go provides a robust and elegant system for handling errors, making your code more reliable and resilient. 6 | 7 | ### The `error` Interface: A Standard for Error Handling 8 | 9 | At the heart of Go's error handling system lies the `error` interface. This interface defines a single method, `Error()`, which returns a string describing the error: 10 | 11 | ```go 12 | type error interface { 13 | Error() string 14 | } 15 | ``` 16 | 17 | Any type that implements the `Error()` method satisfies the `error` interface, making it a standard way to represent errors. 18 | 19 | ### Returning Errors: Signalling Problems 20 | 21 | Functions that might encounter errors typically return an `error` value. This signals to the caller that something went wrong. 22 | 23 | ```go 24 | func divide(a, b int) (int, error) { 25 | if b == 0 { 26 | return 0, errors.New("division by zero") // Return an error if b is zero 27 | } 28 | return a / b, nil // Return the result and nil (no error) if successful 29 | } 30 | ``` 31 | 32 | ### Checking for Errors: Detecting and Handling Problems 33 | 34 | The caller of a function must check for errors. You typically use an `if` statement to check if the error is `nil`, indicating success: 35 | 36 | ```go 37 | result, err := divide(10, 0) // Call the "divide" function 38 | if err != nil { 39 | fmt.Println("Error:", err) // Handle the error if it's not nil 40 | } else { 41 | fmt.Println("Result:", result) // Use the result if there's no error 42 | } 43 | ``` 44 | 45 | ### Custom Errors: Providing More Context 46 | 47 | Go's built-in `errors.New` function is useful for creating basic errors, but you can create custom errors to provide more context and information. 48 | 49 | ```go 50 | type InsufficientFundsError struct { 51 | Balance int 52 | Amount int 53 | } 54 | 55 | func (e InsufficientFundsError) Error() string { 56 | return fmt.Sprintf("Insufficient funds: balance is %d, requested amount is %d", e.Balance, e.Amount) 57 | } 58 | 59 | func withdraw(balance int, amount int) (int, error) { 60 | if balance < amount { 61 | return 0, InsufficientFundsError{Balance: balance, Amount: amount} // Return a custom error 62 | } 63 | return balance - amount, nil 64 | } 65 | ``` 66 | 67 | ### The `errors` Package: Useful Error Handling Utilities 68 | 69 | Go's `errors` package provides helpful functions for working with errors, such as: 70 | 71 | - `errors.New`: Creates a basic error. 72 | - `errors.Wrap`: Wraps an existing error with additional context. 73 | - `errors.Unwrap`: Extracts the underlying error from a wrapped error. 74 | 75 | ### Formatting Error Strings: Clear and Descriptive Messages 76 | 77 | Use `fmt.Errorf` or `fmt.Sprintf` to create error strings that are clear, concise, and informative. Include relevant details, such as the operation that failed or the specific values involved. 78 | 79 | ```go 80 | func openFile(filename string) (io.Reader, error) { 81 | file, err := os.Open(filename) 82 | if err != nil { 83 | return nil, fmt.Errorf("failed to open file %s: %w", filename, err) // Wrap the error with context 84 | } 85 | return file, nil 86 | } 87 | ``` 88 | 89 | You've now explored the core concepts of error handling in Go! 90 | -------------------------------------------------------------------------------- /src/data/extras/clean-package.md: -------------------------------------------------------------------------------- 1 | # Clean Package 2 | 3 | Learning to properly build small and reusable packages can take your Go career to the next level. 4 | 5 | ## Rules Of Thumb 6 | 7 | ### 1. Hide internal logic 8 | 9 | If you're familiar with the pillars of OOP, this is a practice in *encapsulation*. 10 | 11 | Oftentimes an application will have complex logic that requires a lot of code. In almost every case the logic that the application cares about can be exposed via an API, and most of the dirty work can be kept within a package. For example, imagine we are building an application that needs to classify images. We could build a package: 12 | 13 | ```go 14 | package classifier 15 | 16 | // ClassifyImage classifies images as "hotdog" or "not hotdog" 17 | func ClassifyImage(image []byte) (imageType string) { 18 | return hasHotdogColors(image) && hasHotdogShape(image) 19 | } 20 | 21 | func hasHotdogShape(image []byte) bool { 22 | // internal logic that the application doesn't need to know about 23 | return true 24 | } 25 | 26 | func hasHotdogColors(image []byte) bool { 27 | // internal logic that the application doesn't need to know about 28 | return true 29 | } 30 | ``` 31 | 32 | We create an API by only exposing the function(s) that the application-level needs to know about. All other logic is unexported to keep a clean separation of concerns. The application doesn’t need to know how to classify an image, just the result of the classification. 33 | 34 | ### 2. Don’t change APIs 35 | 36 | The unexported functions within a package can and should change often for testing, refactoring, and bug fixing. 37 | 38 | A well-designed library will have a stable API so that users aren’t receiving breaking changes each time they update the package version. In Go, this means not changing exported function’s signatures. 39 | 40 | ### 3. Don’t export functions from the main package 41 | 42 | A `main` package isn't a library, there's no need to export functions from it. 43 | 44 | ### 4. Packages shouldn't know about dependents 45 | 46 | Perhaps one of the most important and most broken rules is that a package shouldn’t know anything about its dependents. In other words, a package should never have specific knowledge about a particular application that uses it. 47 | -------------------------------------------------------------------------------- /src/data/extras/custom-package.md: -------------------------------------------------------------------------------- 1 | # Custom Package 2 | 3 | Let's write a package to import and use in our project. 4 | 5 | First navigate to a folder where we created [our first Go program](/setting-up-environment/your-first-go-program). Then create a sibling directory at the same level as the `hellogo` directory: 6 | 7 | ```bash 8 | mkdir mystrings 9 | cd mystrings 10 | ``` 11 | 12 | Initialize a [module](/setting-up-environment/modules): 13 | 14 | ```bash 15 | go mod init {REMOTE}/{USERNAME}/mystrings 16 | ``` 17 | 18 | Then create a new file `mystrings.go` in that directory and paste the following code: 19 | 20 | ```go 21 | // by convention, we name our package the same as the directory 22 | package mystrings 23 | 24 | // Reverse reverses a string left to right 25 | // Notice that we need to capitlize the first letter of the function 26 | // If we don't then we won't be able access this function outside of the 27 | // mystrings package 28 | func Reverse(s string) string { 29 | result := "" 30 | for _, v := range s { 31 | result = string(v) + result 32 | } 33 | return result 34 | } 35 | ``` 36 | 37 | Note that there is no `main.go` or `func main()` in this package. 38 | 39 | `go build` won't build an executable from a library package. However, `go build` will still compile the package and save it to our local build cache. It's useful for checking for compile errors. 40 | 41 | Run: 42 | 43 | ```bash 44 | go build 45 | ``` 46 | 47 | ## Using the custom package 48 | 49 | Let's use our new `mystrings` package in `hellogo` 50 | 51 | Modify hellogo's `main.go` file: 52 | 53 | ```go 54 | package main 55 | 56 | import ( 57 | "fmt" 58 | 59 | "{REMOTE}/{USERNAME}/mystrings" 60 | ) 61 | 62 | func main() { 63 | fmt.Println(mystrings.Reverse("hello world")) 64 | } 65 | ``` 66 | 67 | Don't forget to replace {REMOTE} and {USERNAME} with the values you used before. Then edit hellogo's `go.mod` file to contain the following: 68 | 69 | ```go 70 | module example.com/username/hellogo 71 | 72 | go 1.20 73 | 74 | replace example.com/username/mystrings v0.0.0 => ../mystrings 75 | 76 | require ( 77 | example.com/username/mystrings v0.0.0 78 | ) 79 | ``` 80 | 81 | Now build and run the new program: 82 | 83 | ```bash 84 | go build 85 | ./hellogo 86 | ``` 87 | -------------------------------------------------------------------------------- /src/data/extras/proverbs.md: -------------------------------------------------------------------------------- 1 | # Go Proverbs 2 | 3 | Similar to the [Zen of Python](https://peps.python.org/pep-0020/), the [Go Proverbs](https://go-proverbs.github.io/) are a beautiful collection of wise words from Rob Pike, one of Go's creators. 4 | 5 | - Don't communicate by sharing memory, share memory by communicating. 6 | - Concurrency is not parallelism. 7 | - Channels orchestrate; mutexes serialize. 8 | - The bigger the interface, the weaker the abstraction. 9 | - Make the zero value useful. 10 | - interface{} says nothing. 11 | - Gofmt's style is no one's favorite, yet gofmt is everyone's favorite. 12 | - A little copying is better than a little dependency. 13 | - Syscall must always be guarded with build tags. 14 | - Cgo must always be guarded with build tags. 15 | - Cgo is not Go. 16 | - With the unsafe package there are no guarantees. 17 | - Clear is better than clever. 18 | - Reflection is never clear. 19 | - Errors are values. 20 | - Don't just check errors, handle them gracefully. 21 | - Design the architecture, name the components, document the details. 22 | - Documentation is for users. 23 | - Don't panic. 24 | -------------------------------------------------------------------------------- /src/data/extras/remote-package.md: -------------------------------------------------------------------------------- 1 | # Remote Package 2 | 3 | Imagine you're building a Go application and you need to perform complex tasks like working with databases, sending emails, or generating PDFs. Building all of these functionalities from scratch would be a monumental task. That's where Go's remote package management comes in, saving you time and effort by providing a vast ecosystem of pre-built, ready-to-use packages. 4 | 5 | ### 1. Go Modules: The Foundation for Package Management 6 | 7 | Go Modules are the official package management system introduced in Go 1.11. They provide a centralized way to define, share, and consume packages, streamlining the development process. 8 | 9 | - `go.mod`: The heart of every Go module is the `go.mod` file. This file acts as a manifest, declaring the module's name, version, and the dependencies it requires. 10 | - `go get`: This command is your primary tool for fetching and installing packages from remote repositories. 11 | 12 | ### 2. Fetching Packages: The `go get` Command 13 | 14 | Let's say you need to work with JSON data in your project. The `encoding/json` package is already part of the standard library, so no need to fetch it. But if you want to use a popular third-party package like `github.com/spf13/cobra` for command-line interface (CLI) building, you'll use `go get`. 15 | 16 | ```go 17 | go get github.com/spf13/cobra 18 | ``` 19 | 20 | This command will: 21 | 22 | 1. Download the package: It fetches the code from the specified Git repository. 23 | 2. Install the package: It places the package in your Go workspace. 24 | 3. Update `go.mod`: It adds the package as a dependency in your `go.mod` file. 25 | 26 | ### 3. Using Packages: Including and Importing 27 | 28 | Once a package is installed, you can use it in your code by importing it. 29 | 30 | ```go 31 | package main 32 | 33 | import ( 34 | "fmt" 35 | "github.com/spf13/cobra" // Import the cobra package 36 | ) 37 | 38 | func main() { 39 | var rootCmd = &cobra.Command{ 40 | Use: "my-cli", 41 | Short: "A simple CLI application", 42 | } 43 | 44 | rootCmd.AddCommand( 45 | // Add subcommands to your CLI here... 46 | ) 47 | 48 | if err := rootCmd.Execute(); err != nil { 49 | fmt.Println(err) 50 | } 51 | } 52 | ``` 53 | 54 | **Code breakdown**: 55 | 56 | - `import "github.com/spf13/cobra"`: This line imports the `cobra` package, making its functions and types available for use in your code. 57 | - `&cobra.Command{}`: This creates a new `cobra.Command` object, which represents your CLI application. 58 | - `rootCmd.Execute()`: This starts the CLI and executes its commands. 59 | 60 | ### 4. Managing Dependencies: `go mod tidy` 61 | 62 | As your project grows, you may add more packages. The `go mod tidy` command keeps your `go.mod` file clean and efficient: 63 | 64 | - Removes unused packages: If a package is no longer used, `go mod tidy` removes it from `go.mod`. 65 | - Adds missing dependencies: If your code requires a package that's not listed in `go.mod`, `go mod tidy` adds it automatically. 66 | 67 | ### 5. Version Control: `go get -u` 68 | 69 | You can specify a specific version of a package using semantic versioning (e.g., `v1.2.3`). 70 | 71 | ```go 72 | go get github.com/spf13/cobra@v1.2.3 73 | ``` 74 | 75 | To update a package to its latest version: 76 | 77 | ```go 78 | go get -u github.com/spf13/cobra 79 | ``` 80 | 81 | ### The Power of a Rich Ecosystem 82 | 83 | - Explore the Go Package Index (GOPROXY): You can browse available packages and their documentation at [https://pkg.go.dev/](https://pkg.go.dev/). 84 | - Contribute to the Community: Consider creating your own Go packages and sharing them with others on GitHub. 85 | -------------------------------------------------------------------------------- /src/data/extras/standard-library.md: -------------------------------------------------------------------------------- 1 | # Standard Library 2 | 3 | The Go standard library is a developer's dream, offering a plethora of pre-built tools and packages to handle just about any task you can imagine. 4 | 5 | Let's dive into some of the most common and useful packages within the standard library. 6 | 7 | ### 1. `net/http`: Your Gateway to the Web 8 | 9 | The `net/http` package is your go-to for creating web servers and interacting with web resources. 10 | 11 | **Example: Building a Simple Web Server:** 12 | 13 | ```go 14 | package main 15 | 16 | import ( 17 | "fmt" 18 | "net/http" 19 | ) 20 | 21 | func handler(w http.ResponseWriter, r *http.Request) { 22 | fmt.Fprintf(w, "Hello, you've requested: %s\n", r.URL.Path) 23 | } 24 | 25 | func main() { 26 | http.HandleFunc("/", handler) // Define a handler for the root path 27 | fmt.Println("Server listening on port 8080") 28 | http.ListenAndServe(":8080", nil) // Start the server 29 | } 30 | ``` 31 | 32 | **Code breakdown:** 33 | 34 | - **`handler(w http.ResponseWriter, r *http.Request)`:** This function handles requests to the server. It writes a response to the `w` object, which represents the HTTP response. `r` holds the incoming request details. 35 | - **`http.HandleFunc("/", handler)`:** This line tells the server to use the `handler` function to respond to requests to the root path (`/`). 36 | - **`http.ListenAndServe(":8080", nil)`:** This starts the server, listening on port `8080`. 37 | 38 | Save this code as `server.go` and run it. Then open your browser and navigate to `http://localhost:8080/` (or any path you want). You'll see the message displayed by the `handler` function. 39 | 40 | ### 2. `os`: Interacting with the Operating System 41 | 42 | The `os` package gives you access to system-level functionalities like working with files, environment variables, processes, and more. 43 | 44 | **Example: Reading a File:** 45 | 46 | ```go 47 | package main 48 | 49 | import ( 50 | "bufio" 51 | "fmt" 52 | "os" 53 | ) 54 | 55 | func main() { 56 | file, err := os.Open("my_file.txt") // Open the file 57 | if err != nil { 58 | fmt.Println("Error opening file:", err) 59 | return 60 | } 61 | defer file.Close() // Close the file when the function exits 62 | 63 | scanner := bufio.NewScanner(file) // Create a scanner for the file 64 | for scanner.Scan() { 65 | line := scanner.Text() 66 | fmt.Println(line) 67 | } 68 | 69 | if err := scanner.Err(); err != nil { 70 | fmt.Println("Error reading file:", err) 71 | } 72 | } 73 | ``` 74 | 75 | **Code breakdown:** 76 | 77 | - **`file, err := os.Open("my_file.txt")`:** This line opens the file named `my_file.txt`. 78 | - **`defer file.Close()`:** The `defer` keyword ensures that the file will be closed after the function finishes, even if an error occurs. 79 | - **`scanner := bufio.NewScanner(file)`:** This creates a scanner that reads the file line by line. 80 | - **`for scanner.Scan() { ... }`:** This loop reads each line and prints it to the console. 81 | 82 | Create a file named `my_file.txt` with some text in it. Then run the code and you'll see the content of the file printed to the console. 83 | 84 | ### 3. `time`: Time and Date Management 85 | 86 | The `time` package lets you work with dates, times, durations, and time zones. 87 | 88 | **Example: Getting Current Time:** 89 | 90 | ```go 91 | package main 92 | 93 | import ( 94 | "fmt" 95 | "time" 96 | ) 97 | 98 | func main() { 99 | now := time.Now() // Get the current time 100 | fmt.Println("Current time:", now) 101 | 102 | fmt.Println("Hour:", now.Hour()) 103 | fmt.Println("Minute:", now.Minute()) 104 | fmt.Println("Second:", now.Second()) 105 | } 106 | ``` 107 | 108 | **Code breakdown:** 109 | 110 | - **`now := time.Now()`:** Gets the current time and stores it in the `now` variable. 111 | - **`fmt.Println("Current time:", now)`:** Prints the current time in its full format. 112 | - **`now.Hour()`, `now.Minute()`, `now.Second()`:** Access specific time components. 113 | 114 | This code will print the current date and time along with its hour, minute, and second. 115 | 116 | ### The Power of the Standard Library 117 | 118 | These are just a few examples of the vast capabilities of the Go standard library. Explore the documentation [https://pkg.go.dev/](https://pkg.go.dev/) for a comprehensive list of packages and their functions. 119 | -------------------------------------------------------------------------------- /src/data/extras/user-input.md: -------------------------------------------------------------------------------- 1 | # User Input 2 | 3 | Imagine your program is a fun chatbot, a helpful calculator, or even a game that needs players to participate. We need a way to let them feed information into our Go programs. 4 | 5 | Let's break down how to do this, piece by piece, using code that's as clear as a sunny day! 6 | 7 | ### 1. The "Scan" Approach: Simple and Straightforward 8 | 9 | The `fmt` package is a Go godsend, packed with tools for input and output. The `fmt.Scanln()` function is your go-to for getting user input. It reads a line from standard input (usually your keyboard) and stores it as a string. 10 | 11 | ```go 12 | package main 13 | 14 | import ( 15 | "fmt" 16 | ) 17 | 18 | func main() { 19 | fmt.Print("What's your name? ") // Ask the user for their name 20 | var name string 21 | fmt.Scanln(&name) // Read the input and store it in the "name" variable 22 | 23 | fmt.Println("Hello,", name, "!") // Greet the user 24 | } 25 | ``` 26 | 27 | **Code breakdown:** 28 | 29 | - **`fmt.Print("What's your name? ")`:** This line displays a prompt on the screen, asking the user for their name. 30 | - **`var name string`:** We declare a variable `name` of type `string` to hold the user's input. 31 | - **`fmt.Scanln(&name)`:** This is the magic! `Scanln` reads the input from the user (until they press Enter) and stores it in the `name` variable. The ampersand `&` is crucial here. It gives `Scanln` the memory address of `name`, so it can directly modify the variable. 32 | 33 | When you execute this program, you'll see the prompt. Type your name and press Enter. The program will then greet you! 34 | 35 | ### 2. The "bufio" Approach: For the Big Leagues 36 | 37 | The `bufio` package lets you work with buffered I/O, which is super helpful when dealing with a lot of input. It's like having a "buffer" to store input before processing it, which can be a performance boost. 38 | 39 | ```go 40 | package main 41 | 42 | import ( 43 | "bufio" 44 | "fmt" 45 | "os" 46 | ) 47 | 48 | func main() { 49 | reader := bufio.NewReader(os.Stdin) // Create a new reader for standard input 50 | 51 | fmt.Print("Enter a number: ") 52 | input, _ := reader.ReadString('\n') // Read a line until newline is encountered 53 | 54 | // Convert the input to an integer 55 | number, err := fmt.Sscan(input, &number) 56 | if err != nil { 57 | fmt.Println("Invalid input!") 58 | return 59 | } 60 | 61 | fmt.Println("You entered:", number) 62 | } 63 | ``` 64 | 65 | **Code breakdown:** 66 | 67 | - **`reader := bufio.NewReader(os.Stdin)`:** Creates a new reader associated with the standard input (your keyboard). 68 | - **`input, _ := reader.ReadString('\n')`:** Reads a line from the user until it encounters a newline character (`\n`). It stores the input in the `input` variable. 69 | - **`number, err := fmt.Sscan(input, &number)`:** This line converts the string input (`input`) into an integer and stores it in the `number` variable. 70 | - **`if err != nil { ... }`:** Error handling! If there's a problem with the conversion (like the user enters a non-numeric value), an error is returned, and the program handles it gracefully. 71 | 72 | Type in a number and hit Enter. The program will confirm your input! 73 | 74 | ### 3. User Input: It's Not Just Text 75 | 76 | So far, we've dealt with text input. But what if you want to get numbers, dates, or even custom types? 77 | 78 | Fear not, Go's got your back! We can use the `fmt.Scanf()` function to directly read formatted input. 79 | 80 | ```go 81 | package main 82 | 83 | import ( 84 | "fmt" 85 | ) 86 | 87 | func main() { 88 | var age int 89 | fmt.Print("How old are you? ") 90 | fmt.Scanf("%d", &age) // %d tells Scanf to expect an integer 91 | 92 | fmt.Println("You are", age, "years old!") 93 | } 94 | ``` 95 | 96 | **Code breakdown:** 97 | 98 | - **`var age int`:** We declare an integer variable `age` to store the input. 99 | - **`fmt.Scanf("%d", &age)`:** This line is where the magic happens. `Scanf` reads input and tries to match it with the format specifier `%d` (for integer). If the input is a valid integer, it is stored in the `age` variable. 100 | 101 | Type in a number representing your age and hit Enter. The program will tell you how old you are! 102 | -------------------------------------------------------------------------------- /src/data/functions/benefits-of-named-returns.md: -------------------------------------------------------------------------------- 1 | # The Benefits of Named Returns 2 | 3 | Named returns have a couple of benefits when it comes to writing idiomatic Go code. 4 | 5 | ## Good For Documentation (Understanding) 6 | 7 | Named return parameters are great for documenting a function. We know what the function is returning directly from its signature, no need for a comment. Named return parameters are particularly important in longer functions with many return values. 8 | 9 | ```go 10 | func calculator(a, b int) (mul, div int, err error) { 11 | if b == 0 { 12 | return 0, 0, errors.New("Can't divide by zero") 13 | } 14 | mul = a * b 15 | div = a / b 16 | return mul, div, nil 17 | } 18 | ``` 19 | 20 | Which is easier to understand than: 21 | 22 | ```go 23 | func calculator(a, b int) (int, int, error) { 24 | if b == 0 { 25 | return 0, 0, errors.New("Can't divide by zero") 26 | } 27 | mul := a * b 28 | div := a / b 29 | return mul, div, nil 30 | } 31 | ``` 32 | 33 | We know *the meaning* of each return value just by looking at the function signature: "`func calculator(a, b int) (mul, div int, err error)`" 34 | 35 | ## Less Code (Sometimes) 36 | 37 | If there are multiple return statements in a function, you don’t need to write all the return values each time, though you *probably* should. When you choose to omit return values, it's called a *naked* return. Naked returns should only be used in short and simple functions. 38 | -------------------------------------------------------------------------------- /src/data/functions/declaration-syntax.md: -------------------------------------------------------------------------------- 1 | # Declaration Syntax 2 | 3 | Developers often wonder why the declaration syntax in Go is different from the tradition established in the C family of languages. 4 | 5 | ## C-Style Syntax 6 | 7 | The C language describes types with an expression including the name to be declared, and states what type that expression will have. 8 | 9 | ```c 10 | int y; 11 | ``` 12 | 13 | The code above declares `y` as an `int`. In general, the type goes on the left and the expression on the right. Interestingly, the creators of the Go language agreed that the C-style of declaring types in signatures gets confusing really fast - take a look at this nightmare. 14 | 15 | ```c 16 | int (*fp)(int (*ff)(int x, int y), int b) 17 | ``` 18 | 19 | ## Go-style Syntax 20 | 21 | Go's declarations are clear, you just read them left to right, just like you would in English. 22 | 23 | ```go 24 | x int 25 | p *int 26 | a [3]int 27 | ``` 28 | 29 | It's nice for more complex signatures, it makes them easier to read. 30 | 31 | ```go 32 | f func(func(int,int) int, int) int 33 | ``` 34 | 35 | ## Reference 36 | 37 | The [following post on the Go blog](https://blog.golang.org/declaration-syntax) is a great resource for further reading on declaration syntax. 38 | -------------------------------------------------------------------------------- /src/data/functions/early-returns.md: -------------------------------------------------------------------------------- 1 | # Early Returns 2 | 3 | Go supports the ability to return early from a function. This is a powerful feature that can clean up code, especially when used as guard clauses. Guard Clauses leverage the ability to `return` early from a function (or `continue` through a loop) to make nested conditionals one-dimensional. Instead of using if/else chains, we just return early from the function at the end of each conditional block. 4 | 5 | ```go 6 | func divide(dividend, divisor int) (int, error) { 7 | if divisor == 0 { 8 | return 0, errors.New("Can't divide by zero") 9 | } 10 | return dividend/divisor, nil 11 | } 12 | ``` 13 | 14 | Error handling in Go naturally encourages developers to make use of guard clauses. Let’s take a look at an exaggerated example of nested conditional logic: 15 | 16 | ```go 17 | func getInsuranceAmount(status insuranceStatus) int { 18 | amount := 0 19 | if !status.hasInsurance(){ 20 | amount = 1 21 | } else { 22 | if status.isTotaled(){ 23 | amount = 10000 24 | } else { 25 | if status.isDented(){ 26 | amount = 160 27 | if status.isBigDent(){ 28 | amount = 270 29 | } 30 | } else { 31 | amount = 0 32 | } 33 | } 34 | } 35 | return amount 36 | } 37 | ``` 38 | 39 | This could be written with guard clauses instead: 40 | 41 | ```go 42 | func getInsuranceAmount(status insuranceStatus) int { 43 | if !status.hasInsurance(){ 44 | return 1 45 | } 46 | if status.isTotaled(){ 47 | return 10000 48 | } 49 | if !status.isDented(){ 50 | return 0 51 | } 52 | if status.isBigDent(){ 53 | return 270 54 | } 55 | return 160 56 | } 57 | ``` 58 | 59 | The example above is *much* easier to read and understand. When writing code, it’s important to try to reduce the cognitive load on the reader by reducing the number of entities they need to think about at any given time. In the first example, if the developer is trying to figure out `when` 270 is returned, they need to think about each branch in the logic tree and try to remember which cases matter and which cases don’t. With the one-dimensional structure offered by guard clauses, it’s as simple as stepping through each case in order. 60 | -------------------------------------------------------------------------------- /src/data/functions/intro.md: -------------------------------------------------------------------------------- 1 | # Functions Intro 2 | 3 | Functions in Go can take zero or more arguments. To make Go code easier to read, the variable type comes *after* the variable name. For example, the following function: 4 | 5 | ```go 6 | func sub(x int, y int) int { 7 | return x - y 8 | } 9 | ``` 10 | 11 | Accepts two integer parameters and returns another integer. Here, "`func sub(x int, y int) int`" is known as the **function signature**. 12 | 13 | ## Multiple Parameters 14 | 15 | When multiple arguments are of the same type, the type only needs to be declared after the last one, assuming they are in order. For example: 16 | 17 | ```go 18 | func add(x, y int) int { 19 | return x + y 20 | } 21 | ``` 22 | 23 | If they are not in order they need to be defined separately. 24 | -------------------------------------------------------------------------------- /src/data/functions/named-returns.md: -------------------------------------------------------------------------------- 1 | # Named Return Values 2 | 3 | Return values may be given names, and if they are, then they are treated the same as if they were new variables defined at the top of the function. Named return values are best thought of as a way to document the purpose of the returned values. According to the [tour of go](https://tour.golang.org/): 4 | 5 | > A return statement without arguments returns the named return values. This is known as a "naked" return. Naked return statements should be used only in short functions. They can harm readability in longer functions. 6 | 7 | ```go 8 | func getCoords() (x, y int){ 9 | // x and y are initialized with zero values 10 | 11 | return // automatically returns x and y 12 | } 13 | ``` 14 | 15 | Is the same as: 16 | 17 | ```go 18 | func getCoords() (int, int){ 19 | var x int 20 | var y int 21 | return x, y 22 | } 23 | ``` 24 | 25 | In the first example, `x` and `y` are the return values. At the end of the function, we could simply write `return` to return the values of those two variables, rather that writing `return x,y`. 26 | 27 | ## Named Return Values - Implicit Returns 28 | 29 | Even though a function has named return values, we can still explicitly return values if we want to. 30 | 31 | ```go 32 | func getCoords() (x, y int){ 33 | return x, y // this is explicit 34 | } 35 | ``` 36 | 37 | Using this explicit pattern we can even overwrite the return values: 38 | 39 | ```go 40 | func getCoords() (x, y int){ 41 | return 5, 6 // this is explicit, x and y are NOT returned 42 | } 43 | ``` 44 | 45 | Otherwise, if we want to return the values defined in the function signature we can just use a naked `return` (blank return): 46 | 47 | ```go 48 | func getCoords() (x, y int){ 49 | return // implicitly returns x and y 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /src/data/functions/return-values.md: -------------------------------------------------------------------------------- 1 | # Return Values 2 | 3 | ## Passing Variables by Value 4 | 5 | Variables in Go are passed by value (except for a few data types we haven't covered yet). "Pass by value" means that when a variable is passed into a function, that function receives a *copy* of the variable. The function is unable to mutate the caller's data. 6 | 7 | ```go 8 | func main(){ 9 | x := 5 10 | increment(x) 11 | 12 | fmt.Println(x) 13 | // still prints 5, because the increment function received a copy of x 14 | } 15 | 16 | func increment(x int){ 17 | x++ 18 | } 19 | ``` 20 | 21 | ## Ignoring Return Values 22 | 23 | A function can return a value that the caller doesn't care about. We can explicitly ignore variables by using an underscore. For example: 24 | 25 | ```go 26 | func getPoint() (x int, y int) { 27 | return 3, 4 28 | } 29 | 30 | // ignore y value 31 | x, _ := getPoint() 32 | ``` 33 | 34 | Even though `getPoint()` returns two values, we can capture the first one and ignore the second. 35 | 36 | ### Why would you ignore a return value? 37 | 38 | There could be many reasons. For example, maybe a function called `getCircle` returns the center point and the radius, but you really only need the radius for your calculation. In that case, you would ignore the center point variable. This is crucial to understand because the Go compiler will throw an error if you have unused variable declarations in your code, so you *need* to ignore anything you don't intend to use. 39 | -------------------------------------------------------------------------------- /src/data/functions/wrapping-up.md: -------------------------------------------------------------------------------- 1 | # Wrapping Up 2 | 3 | ## Functions in Go 4 | 5 | Functions are the heart of any programming language, and Go is no exception. They allow you to break down complex tasks into smaller, manageable units, making your code more organized, efficient, and reusable. 6 | 7 | ### Introduction to Functions: The Building Blocks of Code 8 | 9 | Imagine you need to perform a specific task repeatedly in your program, like calculating the area of a circle or greeting a user. Instead of writing the same code over and over, you can encapsulate it within a function. Functions act as mini-programs within your main program, allowing you to: 10 | 11 | - Modularize Code: Break down your program into smaller, independent units, making it easier to understand and maintain. 12 | - Reuse Code: Avoid repetition by calling the same function multiple times with different inputs. 13 | - Improve Readability: Make your code more organized and easier to follow. 14 | 15 | ### Basic Function Syntax: A Simple Template 16 | 17 | A function in Go is defined using the `func` keyword followed by the function name, its parameters (inputs), and its return values (outputs). Here's a basic template: 18 | 19 | ```go 20 | func functionName(parameter1 type, parameter2 type) returnType { 21 | // Code to be executed within the function 22 | return value // Optional return value 23 | } 24 | ``` 25 | 26 | ### Examples: Bringing Functions to Life 27 | 28 | Let's illustrate with a few examples: 29 | 30 | Greeting Function: 31 | 32 | ```go 33 | func greet(name string) { 34 | fmt.Println("Hello,", name) 35 | } 36 | 37 | func main() { 38 | greet("Alice") // Output: "Hello, Alice" 39 | greet("Bob") // Output: "Hello, Bob" 40 | } 41 | ``` 42 | 43 | Area Calculation Function: 44 | 45 | ```go 46 | func circleArea(radius float64) float64 { 47 | return 3.14159 * radius * radius 48 | } 49 | 50 | func main() { 51 | area := circleArea(5.0) // Calculate the area of a circle with radius 5.0 52 | fmt.Println("Area:", area) // Output: "Area: 78.53975" 53 | } 54 | ``` 55 | 56 | ### Returning Values: Giving Functions Outputs 57 | 58 | Functions can return values to the caller. This allows you to use the results of a function in other parts of your program: 59 | 60 | ```go 61 | func sum(a int, b int) int { // Function that adds two integers 62 | return a + b 63 | } 64 | 65 | func main() { 66 | result := sum(3, 5) // Call the "sum" function and store the result in "result" 67 | fmt.Println("Sum:", result) // Output: "Sum: 8" 68 | } 69 | ``` 70 | 71 | ### Early Returns: Exiting Functions Efficiently 72 | 73 | Sometimes you might need to exit a function before reaching the end. This is where early returns come in handy: 74 | 75 | ```go 76 | func divide(a int, b int) int { 77 | if b == 0 { 78 | return 0 // Handle division by zero 79 | } 80 | return a / b 81 | } 82 | ``` 83 | 84 | ### Function Declaration Syntax: A Deeper Dive 85 | 86 | Go allows you to declare functions in a few different ways: 87 | 88 | - Standard Declaration: The most common way, as shown in the examples above. 89 | - Named Return Values: You can explicitly name the return values of a function. This can be beneficial for readability and organization: 90 | 91 | ```go 92 | func subtract(a int, b int) (result int) { // "result" is the named return value 93 | result = a - b 94 | return // Return the value of "result" implicitly 95 | } 96 | ``` 97 | 98 | ### Benefits of Named Returns: Cleaner and More Readable Code 99 | 100 | Named return values offer a few advantages: 101 | 102 | - Improved Readability: They make your code easier to understand, especially for functions with multiple return values. 103 | - Less Repetition: You don't need to explicitly mention the return values in the `return` statement. 104 | 105 | You've now learned the fundamentals of functions in Go! In future sections, you'll explore more advanced topics like recursion, closures, and how to effectively work with functions in Go's concurrent environment. Armed with this knowledge, you're ready to write clean, organized, and efficient Go code. Happy coding! 106 | -------------------------------------------------------------------------------- /src/data/interfaces/clean-interfaces.md: -------------------------------------------------------------------------------- 1 | # Clean Interfaces 2 | 3 | Writing clean interfaces is *hard*. Frankly, anytime you’re dealing with abstractions in code, the simple can become complex very quickly if you’re not careful. Let’s go over some rules of thumb for keeping interfaces clean. 4 | 5 | ## 1. Keep Interfaces Small 6 | 7 | If there is only one piece of advice that you take away from this article, make it this: keep interfaces small! Interfaces are meant to define the minimal behavior necessary to accurately represent an idea or concept. Here is an example from the standard HTTP package of a larger interface that’s a good example of defining minimal behavior: 8 | 9 | ```go 10 | type File interface { 11 | io.Closer 12 | io.Reader 13 | io.Seeker 14 | Readdir(count int) ([]os.FileInfo, error) 15 | Stat() (os.FileInfo, error) 16 | } 17 | ``` 18 | 19 | Any type that satisfies the interface’s behaviors can be considered by the HTTP package as a *File*. This is convenient because the HTTP package doesn’t need to know if it’s dealing with a file on disk, a network buffer, or a simple `[]byte`. 20 | 21 | ## 2. Interfaces Should Have No Knowledge of Satisfying Types 22 | 23 | An interface should define what is necessary for other types to classify as a member of that interface. They shouldn’t be aware of any types that happen to satisfy the interface at design time. For example, let’s assume we are building an interface to describe the components necessary to define a car. 24 | 25 | ```go 26 | type car interface { 27 | Color() string 28 | Speed() int 29 | IsFiretruck() bool 30 | } 31 | ``` 32 | 33 | `Color()` and `Speed()` make perfect sense, they are methods confined to the scope of a car. `IsFiretruck()` is an anti-pattern. We are forcing all cars to declare whether or not they are firetrucks. In order for this pattern to make any amount of sense, we would need a whole list of possible subtypes. `IsPickup()`, `IsSedan()`, `IsTank()`… where does it end?? Instead, the developer should have relied on the native functionality of type assertion to derive the underlying type when given an instance of the car interface. Or, if a sub-interface is needed, it can be defined as: 34 | 35 | ```go 36 | type firetruck interface { 37 | car 38 | HoseLength() int 39 | } 40 | ``` 41 | 42 | Which inherits the required methods from `car` and adds one additional required method to make the `car` a `firetruck`. 43 | 44 | ## 3. Interfaces Are Not Classes 45 | 46 | - Interfaces are not classes, they are slimmer. 47 | - Interfaces don’t have constructors or deconstructors that require that data is created or destroyed. 48 | - Interfaces aren’t hierarchical by nature, though there is syntactic sugar to create interfaces that happen to be supersets of other interfaces. 49 | - Interfaces define function signatures, but not underlying behavior. Making an interface often won’t DRY up your code in regards to struct methods. For example, if five types satisfy the `fmt.Stringer` interface, they all need their own version of the `String()` function. 50 | 51 | ## Further reading 52 | 53 | [Best Practices for Interfaces in Go](https://victorpierre.dev/blog/five-go-interfaces-best-practices/) 54 | -------------------------------------------------------------------------------- /src/data/interfaces/interfaces-in-go.md: -------------------------------------------------------------------------------- 1 | # Interfaces in Go 2 | 3 | Interfaces are collections of method signatures. A type "implements" an interface if it has all of the methods of the given interface defined on it. In the following example, a "shape" must be able to return its area and perimeter. Both `rect` and `circle` fulfill the interface. 4 | 5 | ```go 6 | type shape interface { 7 | area() float64 8 | perimeter() float64 9 | } 10 | 11 | type rect struct { 12 | width, height float64 13 | } 14 | func (r rect) area() float64 { 15 | return r.width * r.height 16 | } 17 | func (r rect) perimeter() float64 { 18 | return 2*r.width + 2*r.height 19 | } 20 | 21 | type circle struct { 22 | radius float64 23 | } 24 | func (c circle) area() float64 { 25 | return math.Pi * c.radius * c.radius 26 | } 27 | func (c circle) perimeter() float64 { 28 | return 2 * math.Pi * c.radius 29 | } 30 | ``` 31 | 32 | When a type implements an interface, it can then be used as the interface type. 33 | 34 | ## Interface Implementation 35 | 36 | Interfaces are implemented *implicitly*. A type never declares that it implements a given interface. Unlike in many other languages, there is no explicit declaration of intent, there is no "implements" keyword. If an interface exists and a type has the proper methods defined, then the type automatically fulfills that interface. Implicit interfaces *decouple* the definition of an interface from its implementation. You may add methods to a type and in the process be unknowingly implementing various interfaces, and *that's okay*. 37 | 38 | ### Note 39 | 40 | Remember, interfaces are collections of method signatures. A type "implements" an interface if it has all of the methods of the given interface defined on it. 41 | 42 | ```go 43 | type shape interface { 44 | area() float64 45 | } 46 | ``` 47 | 48 | If a type in your code implements an `area` method, with the same signature (e.g. accepts nothing and returns a `float64`), then that object is said to *implement* the `shape` interface. 49 | 50 | ```go 51 | type circle struct{ 52 | radius int 53 | } 54 | 55 | func (c *circle) area() float64 { 56 | return 3.14 * c.radius * c.radius 57 | } 58 | ``` 59 | 60 | This is *different from most other languages*, where you have to *explicitly* assign an interface type to an object, like with Java: 61 | 62 | ```java 63 | class Circle implements Shape 64 | ``` -------------------------------------------------------------------------------- /src/data/interfaces/multiple-interfaces.md: -------------------------------------------------------------------------------- 1 | # Multiple Interfaces 2 | 3 | A type can implement any number of interfaces in Go. For example, the empty interface, `interface{}`, is *always* implemented by every type because it has no requirements. 4 | 5 | ## Name Your Interface Arguments 6 | 7 | Consider the following interface: 8 | 9 | ```go 10 | type Copier interface { 11 | Copy(string, string) int 12 | } 13 | ``` 14 | 15 | Based on the code alone, can you deduce what *kinds* of strings you should pass into the `Copy` function? We know the function signature expects 2 string types, but what are they? Filenames? URLs? Raw string data? For that matter, what the heck is that `int` that's being returned? Let's add some named arguments and return data to make it more clear. 16 | 17 | ```go 18 | type Copier interface { 19 | Copy(sourceFile string, destinationFile string) (bytesCopied int) 20 | } 21 | ``` 22 | 23 | Much better. We can see what the expectations are now. The first argument is the `sourceFile`, the second argument is the `destinationFile`, and `bytesCopied`, an integer, is returned. 24 | -------------------------------------------------------------------------------- /src/data/interfaces/type-assertion.md: -------------------------------------------------------------------------------- 1 | # Type Assertions in Go 2 | 3 | When working with interfaces in Go, every once-in-awhile you'll need access to the underlying type of an interface value. You can cast an interface to its underlying type using a *type assertion*. 4 | 5 | ```go 6 | type shape interface { 7 | area() float64 8 | } 9 | 10 | type circle struct { 11 | radius float64 12 | } 13 | 14 | // "c" is a new circle cast from "s" which is an instance of a shape. 15 | // "ok" is a bool that is true if s was a circle or false if s isn't a circle 16 | c, ok := s.(circle) 17 | ``` 18 | 19 | ## Type Switches 20 | 21 | A *type switch* makes it easy to do several type assertions in a series. A type switch is similar to a regular switch statement, but the cases specify *types* instead of *values*. 22 | 23 | ```go 24 | func printNumericValue(num interface{}) { 25 | switch v := num.(type) { 26 | case int: 27 | fmt.Printf("%T\n", v) 28 | case string: 29 | fmt.Printf("%T\n", v) 30 | default: 31 | fmt.Printf("%T\n", v) 32 | } 33 | } 34 | 35 | func main() { 36 | printNumericValue(1) 37 | // prints "int" 38 | 39 | printNumericValue("1") 40 | // prints "string" 41 | 42 | printNumericValue(struct{}{}) 43 | // prints "struct {}" 44 | } 45 | ``` 46 | 47 | `fmt.Printf("%T\n", v)` prints the *type* of a variable. 48 | -------------------------------------------------------------------------------- /src/data/interfaces/wrapping-up.md: -------------------------------------------------------------------------------- 1 | # Wrapping Up 2 | 3 | ## Interfaces in Go 4 | 5 | Interfaces in Go are a powerful mechanism for achieving abstraction and polymorphism. They define a contract, outlining the methods that a type must implement to satisfy the interface. This allows you to write code that works with different types in a flexible and adaptable way. 6 | 7 | ### Interfaces: Defining a Contract 8 | 9 | Imagine you have different types of animals: cats, dogs, birds, etc. They might have different behaviors, but they all share certain common actions like making sounds or moving. An interface defines these shared actions: 10 | 11 | ```go 12 | type Animal interface { 13 | Speak() string 14 | Move() string 15 | } 16 | ``` 17 | 18 | This `Animal` interface defines two methods: `Speak()` and `Move()`. Any type that implements these methods will satisfy the `Animal` interface. 19 | 20 | ### Implementing Interfaces: Types Conforming to the Contract 21 | 22 | Let's define a `Cat` and a `Dog` type that implement the `Animal` interface: 23 | 24 | ```go 25 | type Cat struct{} 26 | 27 | func (c Cat) Speak() string { 28 | return "Meow" 29 | } 30 | 31 | func (c Cat) Move() string { 32 | return "Walking gracefully" 33 | } 34 | 35 | type Dog struct{} 36 | 37 | func (d Dog) Speak() string { 38 | return "Woof" 39 | } 40 | 41 | func (d Dog) Move() string { 42 | return "Running excitedly" 43 | } 44 | ``` 45 | 46 | Now, both `Cat` and `Dog` satisfy the `Animal` interface because they implement the required methods. 47 | 48 | ### Using Interfaces: Working with Different Types 49 | 50 | The beauty of interfaces is that you can write code that works with any type that implements the interface: 51 | 52 | ```go 53 | func makeAnimalSound(animal Animal) { 54 | fmt.Println(animal.Speak()) 55 | } 56 | 57 | func main() { 58 | cat := Cat{} 59 | dog := Dog{} 60 | 61 | makeAnimalSound(cat) // Output: "Meow" 62 | makeAnimalSound(dog) // Output: "Woof" 63 | } 64 | ``` 65 | 66 | The `makeAnimalSound` function accepts an `Animal` interface as an argument. This means it can work with any type that implements the `Animal` interface, such as `Cat` or `Dog`. 67 | 68 | ### Multiple Interfaces: Embracing Flexibility 69 | 70 | A type can implement multiple interfaces: 71 | 72 | ```go 73 | type Bird struct{} 74 | 75 | func (b Bird) Speak() string { 76 | return "Tweet" 77 | } 78 | 79 | func (b Bird) Fly() string { 80 | return "Soaring through the sky" 81 | } 82 | 83 | func main() { 84 | bird := Bird{} 85 | 86 | makeAnimalSound(bird) // Output: "Tweet" 87 | //makeBirdFly(bird) // You can use bird with a function accepting a Bird interface 88 | } 89 | ``` 90 | 91 | ### Clean Interfaces: Less is More 92 | 93 | Aim for clean and concise interfaces that focus on a specific set of related actions. This makes your interfaces more reusable and adaptable. 94 | 95 | ### Type Assertion: Verifying Interface Implementations 96 | 97 | Sometimes you might need to know the specific type that implements an interface. Type assertion allows you to check and cast an interface value to its underlying type: 98 | 99 | ```go 100 | func main() { 101 | animal := Cat{} 102 | 103 | if cat, ok := animal.(Cat); ok { 104 | fmt.Println("It's a Cat:", cat.Speak()) 105 | } else { 106 | fmt.Println("It's not a Cat") 107 | } 108 | } 109 | ``` 110 | 111 | You've learned the fundamentals of interfaces in Go! If you want to dig more, you can explore advanced topics like embedding interfaces, empty interfaces, and how interfaces are used in Go's concurrency model. With this understanding, you'll be well-equipped to leverage the power of abstraction and polymorphism in your Go programs. 112 | -------------------------------------------------------------------------------- /src/data/intro/basic-syntax.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanuelCh/gopher-notes/2ac2c6a501f409a84481efb0a5a526ba45577dd2/src/data/intro/basic-syntax.md -------------------------------------------------------------------------------- /src/data/intro/compiled-vs-interpreted.md: -------------------------------------------------------------------------------- 1 | # Compiled vs Interpreted 2 | 3 | Compiled programs can be run without access to the original source code, and without access to a compiler. This is different than interpreted languages like Python and JavaScript. With Python and JavaScript the code is interpreted at [runtime](https://en.wikipedia.org/wiki/Runtime_(program_lifecycle_phase)) by a separate program known as the "interpreter". Distributing code for users to run can be a pain because they need to have an interpreter installed, and they need access to the original source code. 4 | 5 | ![compiled vs interpreted](https://i.imgur.com/ovHaWmS.jpg) 6 | 7 | One of the most convenient things about using a compiled language like Go for backend services is that when we deploy our server we don't need to include any runtime language dependencies like Node or a Python interpreter. We just add the pre-compiled binary to the server and start it up! -------------------------------------------------------------------------------- /src/data/intro/compiling.md: -------------------------------------------------------------------------------- 1 | # Compiling 2 | 3 | Go is a compiled programming language. To define the term "compiled", computers need machine code, they don't understand English or even uncompiled computer programs. For example, the code 4 | 5 | ```go 6 | package main 7 | 8 | import "fmt" 9 | 10 | func main(){ 11 | fmt.Println("hello world") 12 | } 13 | ``` 14 | 15 | means *nothing* to a computer. We need to convert our high-level (Go) code into machine language, which is really just a set of instructions that some specific hardware can understand. In your case, your CPU. The Go compiler's job is to take Go code and produce machine code. On Windows, that would be a `.exe` file. On Mac or Linux, it would be any executable file. 16 | 17 | ## Computers Need Machine Code 18 | 19 | A computer's [CPU](https://en.wikipedia.org/wiki/Central_processing_unit) only understands its own *instruction set*, which we call "machine code". Instructions are basic math operations like addition, subtraction, multiplication, and the ability to save data temporarily. For example, an [ARM processor](https://en.wikipedia.org/wiki/ARM_architecture) uses the *ADD* instruction when supplied with the number `0100` in binary. Go, C, and Rust are all languages where the code is first converted to machine code by the compiler before it's executed. 20 | 21 | ![compiler](https://www.astateofdata.com/wp-content/uploads/2019/09/code-compiler-machine-code.png) 22 | 23 | 24 | ## How Fast is Go ? 25 | 26 | Generally speaking, compiled languages run much faster than interpreted languages or VM-powered languages, and Go is no exception. Go is one of the fastest programming languages, beating JavaScript, Python, and Ruby handily in most benchmarks. However, Go code doesn't *run* quite as fast as its compiled Rust and C counterparts. That said, it *compiles* much faster than they do, which makes the developer experience super productive. Unfortunately, there are no swordfights on Go teams... 27 | 28 | ![xkcd compiling](https://imgs.xkcd.com/comics/compiling.png) 29 | -------------------------------------------------------------------------------- /src/data/intro/go-introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction to Go (Golang) 2 | 3 | Go is a statically typed, compiled programming language designed by Google. It's all about simplicity and efficiency, making it super easy to write clean and maintainable code. Think of it as the cool kid on the block that combines the best features of C, C++, and Python, but with its own unique flair! 4 | 5 | ## Key Features of Go 6 | 7 | - **Simplicity:** Go's syntax is clean and easy to read. No more wrestling with complicated language features! 8 | - **Concurrency:** Go makes it a breeze to handle multiple tasks at once with goroutines and channels. It's like having a squad of mini-helpers working in the background! 9 | - **Fast Compilation:** Go compiles super quickly, so you can spend less time waiting and more time coding. Instant gratification, anyone? 10 | - **Garbage Collection:** No need to manually manage memory. Go's garbage collector takes care of that for you, freeing you up to focus on what really matters! 11 | - **Strong Standard Library:** Go comes with a robust standard library that provides tons of built-in functions for common tasks. It's like having a Swiss Army knife in your coding toolkit! 12 | - **Cross-Platform:** Write your code once, and it can run on multiple platforms without a hitch. Go is all about that portability life! 13 | 14 | ## Historical Background 15 | 16 | Go was created in 2007 by Robert Griesemer, Rob Pike, and Ken Thompson at Google. They wanted to address shortcomings in other languages while keeping things simple and efficient. After a couple of years of development, Go was officially released to the public in 2012. Since then, it's gained a massive following and is now used by companies like Google, Uber, and Dropbox. Talk about a glow-up! 17 | 18 | ## Use Cases 19 | 20 | So, what can you actually do with Go? Here are some popular use cases: 21 | 22 | - **Web Development:** Build high-performance web servers and APIs with frameworks like Gin and Echo. Perfect for those who want to create scalable web applications! 23 | - **Cloud Services:** Go is a go-to language for cloud-based applications and microservices. It's lightweight and efficient, making it ideal for distributed systems. 24 | - **DevOps Tools:** Many DevOps tools, like Docker and Kubernetes, are written in Go. If you're into automation and containerization, Go is your jam! 25 | - **Networking:** With its powerful concurrency model, Go excels at building networking tools and applications. Think chat servers, proxies, and more! 26 | 27 | ## A Glance at a Go Program 28 | 29 | ```go 30 | package main 31 | 32 | import "fmt" 33 | 34 | func main() { 35 | fmt.Println("hello world") 36 | } 37 | 38 | ``` 39 | 40 | Here are a few tidbits about the code: 41 | 42 | 1. `package main` lets the Go compiler know that we want this code to compile and run as a standalone program, as opposed to being a library that's imported by other programs. 43 | 2. `import fmt` imports the `fmt` (formatting) package. The formatting package exists in Go's standard library and let's us do things like print text to the console. 44 | 3. `func main()` defines the `main` function. `main` is the name of the function that acts as the entry point for a Go program. -------------------------------------------------------------------------------- /src/data/intro/go-is-strongly-typed.md: -------------------------------------------------------------------------------- 1 | # Go is Strongly Typed 2 | 3 | Go enforces strong and static typing, meaning variables can only have a single type. A `string` variable like "hello world" can not be changed to an `int`, such as the number `3`. One of the biggest benefits of strong typing is that errors can be caught at "compile time". In other words, bugs are more easily caught ahead of time because they are detected when the code is compiled before it even runs. Contrast this with most interpreted languages, where the variable types are dynamic. Dynamic typing can lead to subtle bugs that are hard to detect. With interpreted languages, the code *must* be run (sometimes in production if you are unlucky 😨) to catch syntax and type errors. 4 | 5 | ## Concatenating strings 6 | 7 | Two strings can be [concatenated](https://en.wikipedia.org/wiki/Concatenation) with the `+` operator. Because Go is strongly typed, it won't allow you to concatenate a string variable with a numeric variable. -------------------------------------------------------------------------------- /src/data/intro/go-memory-usage.md: -------------------------------------------------------------------------------- 1 | # Go Programs are Easy on Memory 2 | 3 | Go programs are fairly lightweight. Each program includes a small amount of "extra" code that's included in the executable binary. This extra code is called the [Go Runtime](https://go.dev/doc/faq#runtime). One of the purposes of the Go runtime is to cleanup unused memory at runtime. In other words, the Go compiler includes a small amount of extra logic in every Go program to make it easier for developers to write code that's memory efficient. 4 | 5 | ## Comparison 6 | 7 | As a general rule Java programs use *more* memory than comparable Go programs because Go doesn't use an entire virtual machine to run its programs, just a small runtime. The Go runtime is small enough that it is included directly in each Go program's compiled machine code. As another general rule Rust and C++ programs use slightly *less* memory than Go programs because more control is given to the developer to optimize memory usage of the program. The Go runtime just handles it for us automatically. 8 | 9 | ## Idle Memory Usage 10 | 11 | ![idle memory](https://miro.medium.com/max/1400/1*Ggs-bJxobwZmrbfuoWGpFw.png) 12 | 13 | In the chart above, [Dexter Darwich compares the memory usage](https://medium.com/@dexterdarwich/comparison-between-java-go-and-rust-fdb21bd5fb7c) of three *very* simple programs written in Java, Go, and Rust. As you can see, Go and Rust use *very* little memory when compared to Java. 14 | -------------------------------------------------------------------------------- /src/data/intro/requirements.md: -------------------------------------------------------------------------------- 1 | # Requirements 2 | 3 | Take a look at the ideas below to get the most out of this guide. If you're familiar with Go, ignore this section and read the topic of your choice. 4 | 5 | ## Don't skip the basics 6 | 7 | If it's your first time learning Go, don't skip the basics of the language like variables and types, functions, structs and so. You can then jump around the topics and refer to the guide as per your interest. 8 | 9 | ## Do the exercises 10 | 11 | This one is an important one. Your learning is tested by doing the exercises provided. Navigate to the [exercises section](/exercises) and try to solve as many exercises as possible. Plus, consider doing challenges on [Codewars](https://www.codewars.com) or other similar platforms. 12 | 13 | ## Read the **"wrapping up"** sections 14 | 15 | Each topic has a **"wrapping up"** section where you can get a glimpse of what was covered and analyze the pieces quickly. Even if you skipped a specific topic, you can read the wrapping up section to get familiar with the topic. 16 | 17 | ## Make sure to understand the code snippet 18 | 19 | While following the guide, you might come across lots of code snippets. Make sure to analyze and understand the code snippets. You can also run them on your local machine. Each snippet has a comment so it's easier for you to understand what the code is doing. If still you don't get the code snippet, google about it or ask AI (perks of living in AI era lol). 20 | 21 | ## Track your learning 22 | 23 | To stay organized and use your time efficiently, track your learning. Also consider [golang roadmap](https://roadmap.sh/golang). It provides you with a decent roadmap and an ability to track your progress. Upon completion of this guide, you can move on to more specialized area of Go like a career as a Backend developer or streamlining your devops work. 24 | 25 | That's it mates. Feel free to use the guide as per your need. You can contact me at [amanuelchaka2@gmail.com](mailto:amanuelchaka2@gmail.com) or my [Telegram account](https://t.me/sozoFe). You can also drop an issue on the guide's [GitHub repo](https://github.com/AmanuelCh/gopher-notes). 26 | -------------------------------------------------------------------------------- /src/data/intro/wrapping-up.md: -------------------------------------------------------------------------------- 1 | # Wrapping Up 2 | 3 | So you're ready to dive into the world of Go, a language known for its speed, simplicity, and efficiency. A quick recap on what was covered: 4 | 5 | ## What is Go? 6 | 7 | Go, often affectionately referred to as "Golang," was born from the minds of Google engineers. It was designed to tackle the challenges of building large, complex systems. Think of it as a language that makes your code easy to read, write, and maintain while still being lightning fast. 8 | 9 | ### Go's Basic Syntax: Simplicity is Key 10 | 11 | Go is known for its elegant and straightforward syntax. You'll find it quite intuitive if you've worked with languages like C or Java. Here are a few key elements: 12 | 13 | Hello, World!: The classic first program: 14 | ```go 15 | package main 16 | 17 | import "fmt" 18 | 19 | func main() { 20 | fmt.Println("Hello, World!") 21 | } 22 | ``` 23 | 24 | ### The Power of Compiled Languages 25 | 26 | Go is a compiled language, which means your code is translated directly into machine code (the language your computer understands) before it can be run. This translation process is done by a compiler. 27 | 28 | Benefits of Compiling: 29 | 30 | - Fast Execution: Compiled code runs much faster than interpreted code because it doesn't need to be translated at runtime. 31 | - Efficiency: The compiler optimizes your code for performance 32 | 33 | ### Memory Management: Go Takes Care of the Heavy Lifting 34 | 35 | Go is a garbage-collected language. This means you don't need to manually manage memory (like you might in C or C++). Go's garbage collector automatically cleans up unused memory, saving you from potential memory leaks and making your life easier. 36 | 37 | ### Go's Concurrency: Making Things Happen Simultaneously 38 | 39 | Go is designed for concurrency, meaning you can run multiple tasks at the same time. This is achieved through goroutines, which are lightweight threads that allow you to write highly efficient concurrent programs. 40 | 41 | Ready for More? 42 | 43 | You've now laid a solid foundation in Go! We've explored the language's key features, its simplicity, and its power. In the next sections, you'll dive into more advanced concepts like functions, data structures, and concurrency. 44 | 45 | Go is a language that empowers you to build performant and reliable applications, and you're now equipped with the essential knowledge to get started. Happy coding! 46 | -------------------------------------------------------------------------------- /src/data/loops/continue-and-break.md: -------------------------------------------------------------------------------- 1 | # The Continue and Break Keyword 2 | 3 | ## Continue 4 | 5 | The `continue` keyword stops the current iteration of a loop and continues to the next iteration. `continue` is a powerful way to use the "guard clause" pattern within loops. 6 | 7 | ```go 8 | for i := 0; i < 10; i++ { 9 | if i % 2 == 0 { 10 | continue 11 | } 12 | fmt.Println(i) 13 | } 14 | // 1 15 | // 3 16 | // 5 17 | // 7 18 | // 9 19 | ``` 20 | 21 | ## Break 22 | 23 | The `break` keyword stops the current iteration of a loop and exits the loop. 24 | 25 | ```go 26 | for i := 0; i < 10; i++ { 27 | if i == 5 { 28 | break 29 | } 30 | fmt.Println(i) 31 | } 32 | // 0 33 | // 1 34 | // 2 35 | // 3 36 | // 4 37 | ``` 38 | -------------------------------------------------------------------------------- /src/data/loops/intro.md: -------------------------------------------------------------------------------- 1 | # Loops in Go 2 | 3 | The basic loop in Go is written in standard C-like syntax: 4 | 5 | ```go 6 | for INITIAL; CONDITION; AFTER{ 7 | // do something 8 | } 9 | ``` 10 | 11 | - `INITIAL` is run once at the beginning of the loop and can create variables within the scope of the loop. 12 | - `CONDITION` is checked before each iteration. If the condition doesn't pass then the loop breaks. 13 | - `AFTER` is run after each iteration. 14 | 15 | For example: 16 | 17 | ```go 18 | for i := 0; i < 10; i++ { 19 | fmt.Println(i) 20 | // Prints 0 through 9 21 | } 22 | ``` 23 | 24 | What makes Go different from other languages is that "`for`" is the only looping keyword in the language. Go accomplishes this by using the for keyword in four formats: 25 | 26 | - A complete, C-style for 27 | - A condition-only for 28 | - An infinite for 29 | - for-range 30 | -------------------------------------------------------------------------------- /src/data/loops/omitting-conditions.md: -------------------------------------------------------------------------------- 1 | # Omitting Conditions 2 | 3 | Loops in Go can omit sections of a for loop. For example, the `CONDITION` (middle part) can be omitted which causes the loop to run forever. 4 | 5 | ```go 6 | for INITIAL; ; AFTER { 7 | // do something forever 8 | } 9 | ``` 10 | 11 | you’ll leave off the initialization if it is based on a value calculated before the loop: 12 | 13 | ```go 14 | i := 0 15 | for ; i < 10; i++ { 16 | fmt.Println(i) 17 | } 18 | ``` 19 | 20 | or you’ll leave off the increment because you have a more complicated increment rule 21 | inside the loop: 22 | 23 | ```go 24 | for i := 0; i < 10; { 25 | fmt.Println(i) 26 | if i % 2 == 0 { 27 | i++ 28 | } else { 29 | i+=2 30 | } 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /src/data/loops/operators.md: -------------------------------------------------------------------------------- 1 | # Operators 2 | 3 | Go supports the standard [modulo operator](https://en.wikipedia.org/wiki/Modulo_operation): 4 | 5 | ```go 6 | 7 % 3 // 1 7 | ``` 8 | 9 | Logical [AND operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND): 10 | 11 | ```go 12 | true && false // false 13 | true && true // true 14 | ``` 15 | 16 | Logical [OR operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR): 17 | 18 | ```go 19 | true || false // true 20 | false || false // false 21 | ``` 22 | -------------------------------------------------------------------------------- /src/data/loops/while-loop.md: -------------------------------------------------------------------------------- 1 | # There is No while Loop in Go 2 | 3 | Most programming languages have a concept of a `while` loop. Because Go allows for the omission of sections of a `for` loop, a `while` loop is just a `for` loop that only has a CONDITION. 4 | 5 | ```go 6 | for CONDITION { 7 | // do some stuff while CONDITION is true 8 | } 9 | ``` 10 | 11 | For example: 12 | 13 | ```go 14 | plantHeight := 1 15 | for plantHeight < 5 { 16 | fmt.Println("still growing! current height:", plantHeight) 17 | plantHeight++ 18 | } 19 | fmt.Println("plant has grown to ", plantHeight, "inches") 20 | ``` 21 | 22 | Which prints: 23 | 24 | ```go 25 | still growing! current height: 1 26 | still growing! current height: 2 27 | still growing! current height: 3 28 | still growing! current height: 4 29 | plant has grown to 5 inches 30 | ``` 31 | -------------------------------------------------------------------------------- /src/data/loops/wrapping-up.md: -------------------------------------------------------------------------------- 1 | # Wrapping Up 2 | 3 | ## Looping Through Your Code 4 | 5 | Loops are essential tools in programming for repeating actions or iterating over data. Go provides two primary loop structures: `for` and `for...range`. 6 | 7 | ### Introduction to Loops: Repeating Actions 8 | 9 | Loops allow you to execute a block of code multiple times, either for a predetermined number of iterations or until a specific condition is met. This avoids repetitive code and makes your programs more dynamic. 10 | 11 | ### The `for` Loop: Go's Workhorse 12 | 13 | The `for` loop is the most versatile loop in Go. It has a simple structure: 14 | 15 | ```go 16 | for initialization; condition; post-iteration { 17 | // Code to be executed repeatedly 18 | } 19 | ``` 20 | 21 | - Initialization: Executed once at the beginning of the loop. 22 | - Condition: Evaluated before each iteration. The loop continues as long as the condition is true. 23 | - Post-iteration: Executed after each iteration. 24 | 25 | ### Examples: Bringing Loops to Life 26 | 27 | Counting to 10: 28 | 29 | ```go 30 | for i := 0; i < 10; i++ { 31 | fmt.Println(i) 32 | } 33 | ``` 34 | 35 | Iterating over a slice: 36 | 37 | ```go 38 | numbers := []int{1, 2, 3, 4, 5} 39 | for i := 0; i < len(numbers); i++ { 40 | fmt.Println(numbers[i]) 41 | } 42 | ``` 43 | 44 | ### Loop Operators: Controlling Iteration 45 | 46 | - `++` (Increment): Increases the value of a variable by 1. 47 | - `--` (Decrement): Decreases the value of a variable by 1. 48 | - `+=` (Add and Assign): Adds a value to a variable and assigns the result to the variable. 49 | - `-=` (Subtract and Assign): Subtracts a value from a variable and assigns the result to the variable. 50 | 51 | ### Omitting Loop Conditions: Infinite Loops 52 | 53 | You can omit the condition in a `for` loop to create an infinite loop: 54 | 55 | ```go 56 | for { 57 | // Code that runs indefinitely 58 | } 59 | ``` 60 | 61 | ### The `continue` Keyword: Skipping to the Next Iteration 62 | 63 | The `continue` keyword jumps to the beginning of the next iteration of the loop, skipping any remaining code in the current iteration: 64 | 65 | ```go 66 | for i := 0; i < 10; i++ { 67 | if i%2 == 0 { 68 | continue // Skip even numbers 69 | } 70 | fmt.Println(i) 71 | } 72 | ``` 73 | 74 | ### The `break` Keyword: Exiting the Loop 75 | 76 | The `break` keyword exits the loop completely, ending the loop's execution: 77 | 78 | ```go 79 | for i := 0; i < 10; i++ { 80 | if i == 5 { 81 | break // Exit the loop when i is 5 82 | } 83 | fmt.Println(i) 84 | } 85 | ``` 86 | -------------------------------------------------------------------------------- /src/data/maps/key-types.md: -------------------------------------------------------------------------------- 1 | # Key Types 2 | 3 | Any type can be used as the *value* in a map, but *keys* are more restrictive. Read the following section of the official [Go blog](https://go.dev/blog/maps): 4 | 5 | As mentioned earlier, **map keys may be of any type that is comparable**. The language spec defines this precisely, but in short, comparable types are boolean, numeric, string, pointer, channel, and interface types, and structs or arrays that contain only those types. Notably absent from the list are slices, maps, and functions; these types cannot be compared using `==`, and may not be used as map keys. 6 | 7 | It's obvious that strings, ints, and other basic types should be available as map keys, but perhaps unexpected are struct keys. Struct can be used to key data by multiple dimensions. For example, this map of maps could be used to tally web page hits by country: 8 | 9 | ```go 10 | hits := make(map[string]map[string]int) 11 | ``` 12 | 13 | This is map of string to (map of string to int). Each key of the outer map is the path to a web page with its own inner map. Each inner map key is a two-letter country code. This expression retrieves the number of times an Australian has loaded the documentation page: 14 | 15 | ```go 16 | n := hits["/doc/"]["au"] 17 | ``` 18 | 19 | Unfortunately, this approach becomes unwieldy when adding data, as for any given outer key you must check if the inner map exists, and create it if needed: 20 | 21 | ```go 22 | func add(m map[string]map[string]int, path, country string) { 23 | mm, ok := m[path] 24 | if !ok { 25 | mm = make(map[string]int) 26 | m[path] = mm 27 | } 28 | mm[country]++ 29 | } 30 | add(hits, "/doc/", "au") 31 | ``` 32 | 33 | On the other hand, a design that uses a single map with a struct key does away with all that complexity: 34 | 35 | ```go 36 | type Key struct { 37 | Path, Country string 38 | } 39 | hits := make(map[Key]int) 40 | ``` 41 | 42 | When a Vietnamese person visits the home page, incrementing (and possibly creating) the appropriate counter is a one-liner: 43 | 44 | ```go 45 | hits[Key{"/", "vn"}]++ 46 | ``` 47 | 48 | And it’s similarly straightforward to see how many Swiss people have read the spec: 49 | 50 | ```go 51 | n := hits[Key{"/ref/spec", "ch"}] 52 | ``` 53 | -------------------------------------------------------------------------------- /src/data/maps/maps-in-go.md: -------------------------------------------------------------------------------- 1 | # Maps 2 | 3 | Maps are similar to JavaScript objects, Python dictionaries, and Ruby hashes. Maps are a data structure that provides key -> value mapping. The zero value of a map is `nil`. We can create a map by using a literal or by using the `make()` function: 4 | 5 | ```go 6 | ages := make(map[string]int) 7 | ages["John"] = 37 8 | ages["Mary"] = 24 9 | ages["Mary"] = 21 // overwrites 24 10 | ``` 11 | 12 | ```go 13 | ages := map[string]int{ 14 | "John": 37, 15 | "Mary": 21, 16 | } 17 | ``` 18 | 19 | The `len()` function works on a map, it returns the total number of key/value pairs. 20 | 21 | ```go 22 | ages := map[string]int{ 23 | "John": 37, 24 | "Mary": 21, 25 | } 26 | fmt.Println(len(ages)) // 2 27 | ``` 28 | -------------------------------------------------------------------------------- /src/data/maps/maps-review.md: -------------------------------------------------------------------------------- 1 | # Maps Review 2 | 3 | Like slices, maps hold references to an underlying data structure. If you pass a map to a function that changes the contents of the map, the changes will be visible in the caller. 4 | 5 | ## Map Literals 6 | 7 | Maps can be constructed using the usual composite literal syntax with colon-separated key-value pairs, so it's easy to build them during initialization. 8 | 9 | ```go 10 | var timeZone = map[string]int{ 11 | "UTC": 0*60*60, 12 | "EST": -5*60*60, 13 | "CST": -6*60*60, 14 | "MST": -7*60*60, 15 | "PST": -8*60*60, 16 | } 17 | ``` 18 | 19 | ## Missing keys 20 | 21 | An attempt to fetch a map value with a key that is not present in the map will return the zero value for the type of the entries in the map. For instance, if the map contains integers, looking up a non-existent key will return 0. A set can be implemented as a map with value type bool. Set the map entry to true to put the value in the set, and then test it by simple indexing. 22 | 23 | ```go 24 | attended := map[string]bool{ 25 | "Ann": true, 26 | "Joe": true, 27 | ... 28 | } 29 | 30 | if attended[person] { // will be false if person is not in the map 31 | fmt.Println(person, "was at the meeting") 32 | } 33 | ``` 34 | 35 | Sometimes you need to distinguish a missing entry from a zero value. Is there an entry for "UTC" or is that 0 because it's not in the map at all? You can discriminate with a form of multiple assignment. 36 | 37 | ```go 38 | var seconds int 39 | var ok bool 40 | seconds, ok = timeZone[tz] 41 | ``` 42 | 43 | For obvious reasons, this is called the “comma ok” idiom. In this example, if tz is present, seconds will be set appropriately and ok will be true; if not, seconds will be set to zero and ok will be false. Here's a function that puts it together with a nice error report: 44 | 45 | ```go 46 | func offset(tz string) int { 47 | if seconds, ok := timeZone[tz]; ok { 48 | return seconds 49 | } 50 | log.Println("unknown time zone:", tz) 51 | return 0 52 | } 53 | ``` 54 | 55 | ## Deleting map entries 56 | 57 | To delete a map entry, use the delete built-in function, whose arguments are the map and the key to be deleted. It's safe to do this even if the key is already absent from the map. 58 | 59 | ```go 60 | delete(timeZone, "PDT") // Now on Standard Time 61 | ``` 62 | -------------------------------------------------------------------------------- /src/data/maps/mutating-maps.md: -------------------------------------------------------------------------------- 1 | # Mutations on Maps 2 | 3 | Maps can be mutated. 4 | 5 | ```go 6 | m := map[string]int{ 7 | "John": 37, 8 | "Mary": 21, 9 | } 10 | ``` 11 | 12 | ## Insert an element 13 | 14 | ```go 15 | m[key] = elem 16 | 17 | // example 18 | m["Amanuel"] = 20 19 | 20 | fmt.Println(m) // map[Amanuel:20 John:37 Marry:21] 21 | 22 | ``` 23 | 24 | ## Get an element 25 | 26 | ```go 27 | elem := m[key] 28 | 29 | // example 30 | ageJohn := m["John"] 31 | 32 | fmt.Println(ageJohn) // 37 33 | ``` 34 | 35 | ## Delete an element 36 | 37 | ```go 38 | delete(m, key) 39 | 40 | // example 41 | delete(m, "John") 42 | 43 | fmt.Println(m) // map[Amanuel:20 Marry:21] 44 | ``` 45 | 46 | ## Check if a key exists 47 | 48 | ```go 49 | elem, ok := m[key] 50 | ``` 51 | 52 | If `key` is in `m`, then `ok` is `true`. If not, `ok` is `false`. 53 | 54 | If `key` is not in the map, then `elem` is the zero value for the map's element type. 55 | -------------------------------------------------------------------------------- /src/data/maps/nested-maps.md: -------------------------------------------------------------------------------- 1 | # Nested Maps 2 | 3 | Maps can contain maps, creating a nested structure. For example: 4 | 5 | ```go 6 | map[string]map[string]int 7 | map[rune]map[string]int 8 | map[int]map[string]map[string]int 9 | ``` 10 | 11 | ## Counting Instances 12 | 13 | Remember that you can check if a key is already present in a map by using the second return value from the index operation. 14 | 15 | ```go 16 | names := map[string]int{} 17 | 18 | if _, ok := names["elon"]; !ok { 19 | // if the key doesn't exist yet, 20 | // initialize its value to 0 21 | names["elon"] = 0 22 | } 23 | ``` 24 | -------------------------------------------------------------------------------- /src/data/maps/wrapping-up.md: -------------------------------------------------------------------------------- 1 | # Wrapping Up 2 | 3 | ## Mapping Your Data 4 | 5 | Maps are a powerful data structure in Go for storing key-value pairs, offering a flexible and efficient way to associate data. Think of them as dictionaries, where you can quickly access values based on their unique keys. 6 | 7 | ### Maps in Go: Key-Value Pairs 8 | 9 | Maps are declared using the `map` keyword, followed by the key type and the value type, enclosed in square brackets. 10 | 11 | ```go 12 | ages := map[string]int{ 13 | "Alice": 25, 14 | "Bob": 30, 15 | "Charlie": 28, 16 | } 17 | ``` 18 | 19 | - Key: A unique identifier for accessing a specific value. 20 | - Value: The data associated with the key. 21 | 22 | ### Accessing Values: Retrieving Data by Key 23 | 24 | You use the key to access the corresponding value: 25 | 26 | ```go 27 | aliceAge := ages["Alice"] // Retrieve Alice's age 28 | fmt.Println(aliceAge) // Output: 25 29 | ``` 30 | 31 | ### Mutating Maps: Adding, Updating, and Deleting 32 | 33 | Maps are mutable, allowing you to modify their contents: 34 | 35 | ```go 36 | // Adding a new key-value pair: 37 | ages["David"] = 22 38 | 39 | // Updating a value: 40 | ages["Alice"] = 26 41 | 42 | // Deleting a key-value pair: 43 | delete(ages, "Charlie") 44 | ``` 45 | 46 | ### Checking for Keys: Verifying Existence 47 | 48 | You can use the comma-ok idiom to check if a key exists in a map before accessing its value: 49 | 50 | ```go 51 | if age, ok := ages["Bob"]; ok { 52 | fmt.Println("Bob's age is", age) 53 | } else { 54 | fmt.Println("Bob's age is not available") 55 | } 56 | ``` 57 | 58 | ### Nested Maps: Structuring Data Hierarchically 59 | 60 | You can create nested maps to organize data in a hierarchical way: 61 | 62 | ```go 63 | users := map[string]map[string]string{ 64 | "Alice": {"email": "alice@example.com", "city": "New York"}, 65 | "Bob": {"email": "bob@example.com", "city": "London"}, 66 | } 67 | ``` 68 | 69 | ### Key Types: Supporting a Variety of Keys 70 | 71 | Maps can use various data types for their keys, including strings, integers, structs, and more. 72 | -------------------------------------------------------------------------------- /src/data/mutexes-and-generics/constraints.md: -------------------------------------------------------------------------------- 1 | # Constraints 2 | 3 | Sometimes you need the logic in your generic function to know *something* about the types it operates on. The example we saw in the intro section didn't need to know *anything* about the types in the slice, so we used the built-in `any` constraint: 4 | 5 | ```go 6 | func splitAnySlice[T any](s []T) ([]T, []T) { 7 | mid := len(s)/2 8 | return s[:mid], s[mid:] 9 | } 10 | ``` 11 | 12 | Constraints are just interfaces that allow us to write generics that only operate within the constraint of a given interface type. In the example above, the `any` constraint is the same as the empty interface because it means the type in question can be *anything*. 13 | 14 | ## Creating a custom constraint 15 | 16 | Let's take a look at the example of a `concat` function. It takes a slice of values and concatenates the values into a string. This should work with *any type that can represent itself as a string*, even if it's not a string under the hood. For example, a `user` struct can have a `.String()` that returns a string with the user's name and age. 17 | 18 | ```go 19 | type stringer interface { 20 | String() string 21 | } 22 | 23 | func concat[T stringer](vals []T) string { 24 | result := "" 25 | for _, val := range vals { 26 | // this is where the .String() method is used. That's why we need a more specific 27 | // constraint instead of the any constraint 28 | result += val.String() 29 | } 30 | return result 31 | } 32 | ``` 33 | 34 | ## Interface type lists 35 | 36 | When generics were released, a new way of writing interfaces was also released at the same time! 37 | 38 | We can now simply list a bunch of types to get a new interface/constraint. 39 | 40 | ```go 41 | // Ordered is a type constraint that matches any ordered type. 42 | // An ordered type is one that supports the <, <=, >, and >= operators. 43 | type Ordered interface { 44 | ~int | ~int8 | ~int16 | ~int32 | ~int64 | 45 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | 46 | ~float32 | ~float64 | 47 | ~string 48 | } 49 | ``` 50 | -------------------------------------------------------------------------------- /src/data/mutexes-and-generics/generics-in-go.md: -------------------------------------------------------------------------------- 1 | # Generics in Go 2 | 3 | As we've mentioned, Go does *not* support classes. For a long time, that meant that Go code couldn't easily be reused in many circumstances. For example, imagine some code that splits a slice into 2 equal parts. The code that splits the slice doesn't really care about the *values* stored in the slice. Unfortunately in Go we would need to write it multiple times for each type, which is a very un-[DRY](https://blog.boot.dev/clean-code/dry-code/) thing to do. 4 | 5 | ```go 6 | func splitIntSlice(s []int) ([]int, []int) { 7 | mid := len(s)/2 8 | return s[:mid], s[mid:] 9 | } 10 | ``` 11 | 12 | ```go 13 | func splitStringSlice(s []string) ([]string, []string) { 14 | mid := len(s)/2 15 | return s[:mid], s[mid:] 16 | } 17 | ``` 18 | 19 | In Go 1.20 however, support for [generics](https://blog.boot.dev/golang/how-to-use-golangs-generics/) was released, effectively solving this problem! 20 | 21 | ## Type Parameters 22 | 23 | Put simply, generics allow us to use variables to refer to specific types. This is an amazing feature because it allows us to write abstract functions that drastically reduce code duplication. 24 | 25 | ```go 26 | func splitAnySlice[T any](s []T) ([]T, []T) { 27 | mid := len(s)/2 28 | return s[:mid], s[mid:] 29 | } 30 | ``` 31 | 32 | In the example above, `T` is the name of the type parameter for the `splitAnySlice` function, and we've said that it must match the `any` constraint, which means it can be anything. This makes sense because the body of the function *doesn't care* about the types of things stored in the slice. 33 | 34 | ```go 35 | firstInts, secondInts := splitAnySlice([]int{0, 1, 2, 3}) 36 | fmt.Println(firstInts, secondInts) 37 | ``` 38 | 39 | ## Why Generics? 40 | 41 | ### Generics reduce repetitive code 42 | 43 | You should care about generics because they mean you don’t have to write as much code! It can be frustrating to write the same logic over and over again, just because you have some underlying data types that are slightly different. 44 | 45 | ### Generics are used more often in libraries and packages 46 | 47 | Generics give Go developers an elegant way to write amazing utility packages. While you will see and use generics in application code, I think it will much more common to see generics used in libraries and packages. Libraries and packages contain importable code intended to be used in *many* applications, so it makes sense to write them in a more abstract way. Generics are often the way to do just that! 48 | 49 | ### Why did it take so long to get generics? 50 | 51 | Go places an emphasis on simplicity. In other words, Go has purposefully left out many features to provide its best feature: being simple and easy to work with. 52 | 53 | According to [historical data from Go surveys](https://go.dev/blog/survey2020-results), Go’s lack of generics has always been listed as one of the top three biggest issues with the language. At a certain point, the drawbacks associated with the lack of a feature like generics justify adding complexity to the language. 54 | -------------------------------------------------------------------------------- /src/data/mutexes-and-generics/more-on-mutexes.md: -------------------------------------------------------------------------------- 1 | # More on mutexes 2 | 3 | The principle problem that mutexes help us avoid is the *concurrent read/write problem*. This problem arises when one thread is writing to a variable while another thread is reading from that same variable *at the same time*. 4 | 5 | When this happens, a Go program will panic because the reader could be reading bad data while it's being mutated in place. 6 | 7 | ![mutex](https://i.imgur.com/NGBnMXe.png) 8 | 9 | ## Mutex example 10 | 11 | ```go 12 | package main 13 | 14 | import ( 15 | "fmt" 16 | ) 17 | 18 | func main() { 19 | m := map[int]int{} 20 | go writeLoop(m) 21 | go readLoop(m) 22 | 23 | // stop program from exiting, must be killed 24 | block := make(chan struct{}) 25 | <-block 26 | } 27 | 28 | func writeLoop(m map[int]int) { 29 | for { 30 | for i := 0; i < 100; i++ { 31 | m[i] = i 32 | } 33 | } 34 | } 35 | 36 | func readLoop(m map[int]int) { 37 | for { 38 | for k, v := range m { 39 | fmt.Println(k, "-", v) 40 | } 41 | } 42 | } 43 | ``` 44 | 45 | The example above creates a map, then starts two goroutines which each have access to the map. One goroutine continuously mutates the values stored in the map, while the other prints the values it finds in the map. 46 | 47 | If we run the program on a multi-core machine, we get the following output: `fatal error: concurrent map iteration and map write` 48 | 49 | In Go, it isn’t safe to read from and write to a map at the same time. 50 | 51 | ## Mutexes to the rescue 52 | 53 | ```go 54 | package main 55 | 56 | import ( 57 | "fmt" 58 | "sync" 59 | ) 60 | 61 | func main() { 62 | m := map[int]int{} 63 | 64 | mux := &sync.Mutex{} 65 | 66 | go writeLoop(m, mux) 67 | go readLoop(m, mux) 68 | 69 | // stop program from exiting, must be killed 70 | block := make(chan struct{}) 71 | <-block 72 | } 73 | 74 | func writeLoop(m map[int]int, mux *sync.Mutex) { 75 | for { 76 | for i := 0; i < 100; i++ { 77 | mux.Lock() 78 | m[i] = i 79 | mux.Unlock() 80 | } 81 | } 82 | } 83 | 84 | func readLoop(m map[int]int, mux *sync.Mutex) { 85 | for { 86 | mux.Lock() 87 | for k, v := range m { 88 | fmt.Println(k, "-", v) 89 | } 90 | mux.Unlock() 91 | } 92 | } 93 | ``` 94 | 95 | In this example, we added a `sync.Mutex{}` and named it `mux`. In the write loop, the `Lock()` method is called before writing, and then the `Unlock()` is called when we're done. This Lock/Unlock sequence ensures that no other threads can `Lock()` the mutex while *we* have it locked – any other threads attempting to `Lock()` will block and wait until we `Unlock()`. 96 | 97 | In the reader, we `Lock()` before iterating over the map, and likewise `Unlock()` when we're done. Now the threads share the memory safely! 98 | 99 | > Mutex is short for [mutual exclusion](https://en.wikipedia.org/wiki/Mutual_exclusion), and the conventional name for the data structure that provides it is "mutex", often abbreviated to "mux". 100 | 101 | It's called "mutual exclusion" because a mutex *excludes* different threads (or goroutines) from accessing the same data at the same time. 102 | -------------------------------------------------------------------------------- /src/data/mutexes-and-generics/mutexes-in-go.md: -------------------------------------------------------------------------------- 1 | # Mutexes in Go 2 | 3 | Mutexes allow us to *lock* access to data. This ensures that we can control which goroutines can access certain data at which time. 4 | 5 | Go's standard library provides a built-in implementation of a mutex with the [sync.Mutex](https://pkg.go.dev/sync#Mutex) type and its two methods: 6 | 7 | * [.Lock()](https://golang.org/pkg/sync/#Mutex.Lock) 8 | * [.Unlock()](https://golang.org/pkg/sync/#Mutex.Unlock) 9 | 10 | We can protect a block of code by surrounding it with a call to `Lock` and `Unlock` as shown on the `protected()` method below. 11 | 12 | It's good practice to structure the protected code within a function so that `defer` can be used to ensure that we never forget to unlock the mutex. 13 | 14 | ```go 15 | func protected(){ 16 | mux.Lock() 17 | defer mux.Unlock() 18 | // the rest of the function is protected 19 | // any other calls to `mux.Lock()` will block 20 | } 21 | ``` 22 | 23 | Mutexes are powerful. Like most powerful things, they can also cause many bugs if used carelessly. 24 | 25 | ## Maps are not thread-safe 26 | 27 | Maps are **not** safe for concurrent use! If you have multiple goroutines accessing the same map, and at least one of them is writing to the map, you must lock your maps with a mutex. 28 | 29 | > In reality, any Go code you write *may or may not* run on a single-core machine, so it's always best to write your code so that it is safe no matter which hardware it runs on. 30 | -------------------------------------------------------------------------------- /src/data/mutexes-and-generics/parametric-constraints.md: -------------------------------------------------------------------------------- 1 | # Parametric Constraints 2 | 3 | Your interface definitions, which can later be used as constraints, can accept type parameters as well. 4 | 5 | ```go 6 | // The store interface represents a store that sells products. 7 | // It takes a type parameter P that represents the type of products the store sells. 8 | type store[P product] interface { 9 | Sell(P) 10 | } 11 | 12 | type product interface { 13 | Price() float64 14 | Name() string 15 | } 16 | 17 | type book struct { 18 | title string 19 | author string 20 | price float64 21 | } 22 | 23 | func (b book) Price() float64 { 24 | return b.price 25 | } 26 | 27 | func (b book) Name() string { 28 | return fmt.Sprintf("%s by %s", b.title, b.author) 29 | } 30 | 31 | type toy struct { 32 | name string 33 | price float64 34 | } 35 | 36 | func (t toy) Price() float64 { 37 | return t.price 38 | } 39 | 40 | func (t toy) Name() string { 41 | return t.name 42 | } 43 | 44 | // The bookStore struct represents a store that sells books. 45 | type bookStore struct { 46 | booksSold []book 47 | } 48 | 49 | // Sell adds a book to the bookStore's inventory. 50 | func (bs *bookStore) Sell(b book) { 51 | bs.booksSold = append(bs.booksSold, b) 52 | } 53 | 54 | // The toyStore struct represents a store that sells toys. 55 | type toyStore struct { 56 | toysSold []toy 57 | } 58 | 59 | // Sell adds a toy to the toyStore's inventory. 60 | func (ts *toyStore) Sell(t toy) { 61 | ts.toysSold = append(ts.toysSold, t) 62 | } 63 | 64 | // sellProducts takes a store and a slice of products and sells 65 | // each product one by one. 66 | func sellProducts[P product](s store[P], products []P) { 67 | for _, p := range products { 68 | s.Sell(p) 69 | } 70 | } 71 | 72 | func main() { 73 | bs := bookStore{ 74 | booksSold: []book{}, 75 | } 76 | 77 | // By passing in "book" as a type parameter, we can use the sellProducts function to sell books in a bookStore 78 | sellProducts[book](&bs, []book{ 79 | { 80 | title: "The Hobbit", 81 | author: "J.R.R. Tolkien", 82 | price: 10.0, 83 | }, 84 | { 85 | title: "The Lord of the Rings", 86 | author: "J.R.R. Tolkien", 87 | price: 20.0, 88 | }, 89 | }) 90 | fmt.Println(bs.booksSold) 91 | 92 | // We can then do the same for toys 93 | ts := toyStore{ 94 | toysSold: []toy{}, 95 | } 96 | sellProducts[toy](&ts, []toy{ 97 | { 98 | name: "Lego", 99 | price: 10.0, 100 | }, 101 | { 102 | name: "Barbie", 103 | price: 20.0, 104 | }, 105 | }) 106 | fmt.Println(ts.toysSold) 107 | } 108 | ``` 109 | 110 | ## Naming Generic Types 111 | 112 | Let's look at this simple example again: 113 | 114 | ```go 115 | func splitAnySlice[T any](s []T) ([]T, []T) { 116 | mid := len(s)/2 117 | return s[:mid], s[mid:] 118 | } 119 | ``` 120 | 121 | Remember, `T` is just a variable name, We could have named the type parameter *anything*. `T` happens to be a fairly common convention for a type variable, similar to how `i` is a convention for index variables in loops. 122 | 123 | This is just as valid: 124 | 125 | ```go 126 | func splitAnySlice[MyAnyType any](s []MyAnyType) ([]MyAnyType, []MyAnyType) { 127 | mid := len(s)/2 128 | return s[:mid], s[mid:] 129 | } 130 | ``` 131 | -------------------------------------------------------------------------------- /src/data/mutexes-and-generics/rw-mutex-review.md: -------------------------------------------------------------------------------- 1 | # Read - Write Mutex Review 2 | 3 | Maps are safe for concurrent *read* access, just not concurrent read/write or write/write access. A read/write mutex allows all the readers to access the map at the same time, but a writer will still lock out all other readers and writers. 4 | 5 | ```go 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "sync" 11 | ) 12 | 13 | func main() { 14 | m := map[int]int{} 15 | 16 | mux := &sync.RWMutex{} 17 | 18 | go writeLoop(m, mux) 19 | go readLoop(m, mux) 20 | go readLoop(m, mux) 21 | go readLoop(m, mux) 22 | go readLoop(m, mux) 23 | 24 | // stop program from exiting, must be killed 25 | block := make(chan struct{}) 26 | <-block 27 | } 28 | 29 | func writeLoop(m map[int]int, mux *sync.RWMutex) { 30 | for { 31 | for i := 0; i < 100; i++ { 32 | mux.Lock() 33 | m[i] = i 34 | mux.Unlock() 35 | } 36 | } 37 | } 38 | 39 | func readLoop(m map[int]int, mux *sync.RWMutex) { 40 | for { 41 | mux.RLock() 42 | for k, v := range m { 43 | fmt.Println(k, "-", v) 44 | } 45 | mux.RUnlock() 46 | } 47 | } 48 | ``` 49 | 50 | By using a `sync.RWMutex`, our program becomes *more efficient*. We can have as many `readLoop()` threads as we want, while still ensuring that the writers have exclusive access. 51 | -------------------------------------------------------------------------------- /src/data/mutexes-and-generics/rw-mutex.md: -------------------------------------------------------------------------------- 1 | # RW Mutex 2 | 3 | The standard library also exposes a [sync.RWMutex](https://golang.org/pkg/sync/#RWMutex) 4 | 5 | In addition to these methods: 6 | 7 | - [Lock()](https://golang.org/pkg/sync/#Mutex.Lock) 8 | - [Unlock()](https://golang.org/pkg/sync/#Mutex.Unlock) 9 | 10 | The `sync.RWMutex` also has these methods: 11 | 12 | - [RLock()](https://golang.org/pkg/sync/#RWMutex.RLock) 13 | - [RUnlock()](https://golang.org/pkg/sync/#RWMutex.RUnlock) 14 | 15 | The `sync.RWMutex` can help with performance if we have a read-intensive process. Many goroutines can safely read from the map at the same time (multiple `Rlock()` calls can happen simultaneously). However, only one goroutine can hold a `Lock()` and all `RLock()`'s will also be excluded. -------------------------------------------------------------------------------- /src/data/mutexes-and-generics/wrapping-up.md: -------------------------------------------------------------------------------- 1 | # Wrapping Up 2 | 3 | ## Mutexes and Generics 4 | 5 | As your programs grow more complex and involve concurrent access to shared resources, ensuring data consistency and preventing race conditions becomes crucial. Go provides powerful tools to manage concurrent access, and generics add a layer of flexibility to your code. 6 | 7 | ### Mutexes in Go: Protecting Shared Resources 8 | 9 | Mutexes (mutual exclusion locks) are used to control access to shared data, ensuring that only one goroutine can modify the data at a time. This prevents race conditions and ensures data integrity. 10 | 11 | ```go 12 | var mutex sync.Mutex // Create a mutex 13 | 14 | // Locking 15 | mutex.Lock() // Acquire the lock 16 | 17 | // Access the shared data here 18 | mutex.Unlock() // Release the lock 19 | ``` 20 | 21 | ### More on Mutexes: Understanding the Mechanics 22 | 23 | - Deadlock: Occurs when multiple goroutines are blocked waiting for each other to release locks, resulting in a standstill. 24 | - Lock Order: Consistent locking order is crucial for preventing deadlock, ensuring that goroutines always acquire locks in the same sequence. 25 | - Unlocking: Always unlock a mutex after accessing shared data, even if an error occurs. 26 | 27 | ### Read/Write Mutexes: Optimizing for Concurrent Access 28 | 29 | Read/write mutexes provide a more granular level of access control. They allow multiple readers to access the data concurrently, while only one writer is allowed to modify it at a time. 30 | 31 | ```go 32 | var rwMutex sync.RWMutex // Create a read/write mutex 33 | 34 | // Reading 35 | rwMutex.RLock() // Acquire the read lock 36 | 37 | // Access the shared data here 38 | rwMutex.RUnlock() // Release the read lock 39 | 40 | // Writing 41 | rwMutex.Lock() // Acquire the write lock 42 | 43 | // Modify the shared data here 44 | rwMutex.Unlock() // Release the write lock 45 | ``` 46 | 47 | ### Generics in Go: Flexible Code with Type Parameters 48 | 49 | Generics enable you to write functions and data structures that can work with different data types without requiring explicit type specifications. This promotes code reusability and maintainability. 50 | 51 | ```go 52 | func sum[T int | float64](values []T) T { 53 | total := values[0] 54 | for _, value := range values[1:] { 55 | total += value 56 | } 57 | return total 58 | } 59 | 60 | intSum := sum([]int{1, 2, 3}) // Sum of integers 61 | floatSum := sum([]float64{1.5, 2.5, 3.5}) // Sum of floats 62 | ``` 63 | 64 | ### Constraints: Specifying Type Requirements 65 | 66 | Constraints define the types that a generic function or data structure can accept. They enforce type safety and ensure that your code works correctly with the specified types. 67 | 68 | ```go 69 | func sum[T constraints.Ordered](values []T) T { 70 | // ... implementation ... 71 | } 72 | ``` 73 | 74 | ### Parametric Constraints: Specifying Relationships 75 | 76 | Parametric constraints enable you to define relationships between type parameters. This allows you to create more flexible and expressive generics. 77 | 78 | ```go 79 | func equal[T comparable](a, b T) bool { 80 | return a == b 81 | } 82 | ``` 83 | -------------------------------------------------------------------------------- /src/data/setting-up-environment/choose-your-tools.md: -------------------------------------------------------------------------------- 1 | # Choose Your Tools 2 | 3 | While you wrote a small Go program using nothing more than a text editor and the go command, you’ll probably want more advanced tools when working on larger projects. Go IDEs provide many advantages over text editors, including automatic formatting on save, code completion, type checking, error reporting, and integrated debugging. If you don’t already have a favorite tool, two of the most popular Go development environments are **Visual Studio** Code and **GoLand**. 4 | 5 | ## Visual Studio Code 6 | 7 | If you are looking for a free development environment, Visual Studio Code from Microsoft is your best option. Since it was released in 2015, VS Code has become the most popular source code editor for developers. It does not ship with Go support, but you can make it a Go development environment by downloading the Go extension from the extensions gallery. VS Code’s Go support relies on third-party extensions that are accessed via its built-in Marketplace. This includes the Go Development tools, the Delve debugger, and gopls, a Go language server developed by the Go team. While you need to install the Go compiler yourself, the Go extension will install Delve and gopls for you. 8 | 9 | > What is a language server? It’s a standard specification for an API that enables editors to implement intelligent editing behavior, like code completion, quality checks, or finding all the places a variable or function is used in your code. 10 | 11 | ## GoLand 12 | 13 | GoLand is the Go-specific IDE from JetBrains. While JetBrains is best known for Java-centric tools, GoLand is an excellent Go development environment. GoLand’s user interface looks similar to IntelliJ, PyCharm, RubyMine, WebStorm, Android Studio, or any of the other JetBrains IDEs. Its Go support includes refactoring, syntax highlighting, code completion and navigation, documentation pop-ups, a debugger, code coverage, and more. In addition to Go support, GoLand includes JavaScript/HTML/CSS and SQL database tools. Unlike VS Code, GoLand doesn’t require you to install a plug-in to get it to work. If you have already subscribed to IntelliJ Ultimate, you can add Go support via a plug-in. While GoLand is commercial software, JetBrains has a Free License Program for students and core open source contributors. If you don’t qualify for a free license, a 30-day free trial is available. After that, you have to pay for GoLand. 14 | 15 | ## The Go Playground 16 | 17 | There’s one more important tool for Go development, but this is one that you don’t install. Visit The [Go Playground](https://go.dev/play) and you’ll see a window that provides a quick editor for Go. If you have used a command-line environment like irb, node, or python, you’ll find the Go Playground has a similar feel. It gives you a place to try out and share small programs. Enter your program into the window and click the Run button to execute the code. The Format button runs go fmt on your program and updates your imports. The Share button creates a unique URL that you can send to someone else to take a look at your program or to come back to your code at a future date (the URLs have proven to be persistent for a long time, but I wouldn’t rely on the playground as your source code repository). 18 | 19 | > Do not put sensitive information (such as personally identifiable information, passwords, or private keys) into your playground! If you click the Share button, the information is saved on Google’s servers and is accessible to anyone who has the associated Share URL. If you do this by accident, contact Google at security@golang.org with the URL and the reason the content needs to be removed. -------------------------------------------------------------------------------- /src/data/setting-up-environment/go-tooling.md: -------------------------------------------------------------------------------- 1 | # Go Tooling 2 | 3 | All of the Go development tools are accessed via the go command. In addition to `go version` and `go run`, there’s a compiler (`go build`), code formatter (`go fmt`), dependency manager (`go mod`), test runner (`go test`), a tool that scans for common coding mistakes (`go vet`), and more. 4 | 5 | ## Go Build 6 | 7 | `go build` compiles go code into an executable program. Ensure you are in your hellogo repo, then run: 8 | 9 | ```bash 10 | go build 11 | ``` 12 | 13 | Run the new program: 14 | 15 | ```bash 16 | ./hellogo 17 | ``` 18 | 19 | ## Go Install 20 | 21 | Ensure you are in your `hellogo` repo, then run: 22 | 23 | ```bash 24 | go install 25 | ``` 26 | 27 | Navigate out of your project directory: 28 | 29 | ```bash 30 | cd ../ 31 | ``` 32 | 33 | Go has installed the `hellogo` program globally. Run it with: 34 | 35 | ```bash 36 | hellogo 37 | ``` 38 | 39 | > If you get an error regarding "hellogo not found" it means you probably don't have your Go environment setup properly. Specifically, `go install` is adding your binary to your `GOBIN` directory, but that may not be in your `PATH`. You can read more about that here in the [go install docs](https://pkg.go.dev/cmd/go#hdr-Compile_and_install_packages_and_dependencies). 40 | 41 | ## Go fmt 42 | 43 | One of the chief design goals for Go was to create a language that allowed you to write code efficiently. This meant having simple syntax and a fast compiler. It also led Go’s authors to reconsider code formatting. Most languages allow a great deal of flexibility in the way code is formatted. Go does not. Enforcing a standard format makes it a great deal easier to write tools that manipulate source code. This simplifies the compiler and allows the creation of some clever tools for generating code. There is a secondary benefit as well. Developers have historically wasted extraordinary amounts of time on format wars. Since Go defines a standard way of formatting code, Go developers avoid arguments over brace style and tabs versus spaces. For example, Go programs use tabs to indent, and it is a syntax error if the opening brace is not on the same line as the declaration or command that begins the block. 44 | 45 | > Many Go developers think the Go team defined a standard format as a way to avoid developer arguments and discovered the tooling advantages later. However, Russ Cox, the development lead for Go, has publicly stated that better tooling was his original motivation. 46 | 47 | The Go development tools include a command, `go fmt`, which automatically fixes the whitespace in your code to match the standard format. However, it can’t fix braces on the wrong line. Run it with the following: 48 | 49 | ```bash 50 | go fmt ./... 51 | ``` 52 | 53 | Using `./...` tells a Go tool to apply the command to all the files in the current 54 | directory and all subdirectories 55 | 56 | > Remember to run `go fmt` before you compile your code, and, at the very least, before you commit source code changes to your repository! If you forget, make a separate commit that does only `go fmt ./...` so you don’t hide logic changes in an avalanche of formatting changes. 57 | -------------------------------------------------------------------------------- /src/data/setting-up-environment/installing-go-tools.md: -------------------------------------------------------------------------------- 1 | # Installing Go Tools 2 | 3 | To build Go code, you need to download and install the Go development tools. You 4 | can find the latest version of the tools at the downloads page on the [Go website](https://go.dev/dl). Choose the download for your platform and install it. The `.pkg` installer for Mac 5 | and the `.msi` installer for Windows automatically install Go in the correct location, 6 | remove any old installations, and put the Go binary in the default executable path. If you are on a Mac, you can install Go using [Homebrew](https://brew.sh) with the command 7 | 8 | ```bash 9 | brew install go 10 | ``` 11 | 12 | Windows developers who use [Chocolatey](https://chocolatey.org) can install Go with the command 13 | 14 | ```bash 15 | choco install golang 16 | ``` 17 | 18 | The various Linux and BSD installers are gzipped TAR files and expand to a directory named go. Copy this directory to `/usr/local` and add `/usr/local/go/bin` to your `$PATH` so that the go command is accessible: 19 | 20 | ```bash 21 | $ tar -C /usr/local -xzf go1.20.5.linux-amd64.tar.gz 22 | $ echo 'export PATH=$PATH:/usr/local/go/bin' >> $HOME/.bash_profile 23 | $ source $HOME/.bash_profile 24 | ``` 25 | 26 | You might need root permissions to write to /usr/local. If the tar command fails, rerun it with sudo tar -C /usr/local -xzf go1.20.5.linux-amd64.tar.gz. You can validate that your environment is set up correctly by opening up a terminal or command prompt and typing: 27 | 28 | ```bash 29 | $ go version 30 | ``` 31 | 32 | If everything is set up correctly, you should see something like this printed: 33 | 34 | ```bash 35 | go version go1.20.5 darwin/arm64 36 | ``` 37 | 38 | This tells you that this is Go version 1.20.5 on macOS. (Darwin is the operating system at the heart of macOS, and arm64 is the name for the 64-bit chips based on ARM’s designs.) On x64 Linux, you would see: 39 | 40 | ```bash 41 | go version go1.20.5 linux/amd64 42 | ``` 43 | 44 | ## Troubleshooting Your Go Installation 45 | 46 | If you get an error instead of the version message, it’s likely that you don’t have go in your executable path, or you have another program named go in your path. On macOS and other Unix-like systems, use `which go` to see the go command being executed, if any. If nothing is returned, you need to fix your executable path. If you’re on Linux or BSD, it’s possible you installed the 64-bit Go development tools 47 | on a 32-bit system or the development tools for the wrong chip architecture. 48 | 49 | > Go programs compile to a single native binary and do not require any additional software to be installed in order to run them. This is in contrast to languages like Java, Python, and JavaScript, which require you to install a virtual machine to run your program. Using a single native binary makes it a lot easier to distribute programs written in Go. Developers who use Docker or Kubernetes can often package a Go app inside a scratch or distroless image. 50 | 51 | This guide will assume you are on a Unix environment like Linux or Mac. If you're on Windows you may have to use `powershell` or do just a *bit* of Google-ing or ask in Discord to figure out how some commands translate to Windows. 52 | If you are on Windows, I'd optionally recommend checking out [WSL (Windows Subsystem for Linux)](https://docs.microsoft.com/en-us/windows/wsl/install) so that you can work in a Unix environment on your local machine. 53 | 54 | > Make sure to use at least Go version `1.20`. 55 | -------------------------------------------------------------------------------- /src/data/setting-up-environment/modules.md: -------------------------------------------------------------------------------- 1 | # Modules 2 | 3 | Go programs are organized into *packages*. A package is a directory of Go code that's all compiled together. Functions, types, variables, and constants defined in one source file are visible to **all other source files within the same package (directory)**. A *repository* contains one or more *modules*. A module is a collection of Go packages that are released together. 4 | 5 | ## Go Modules 6 | 7 | A Go repository typically contains only one module, located at the root of the repository. A file named `go.mod` at the root of a project declares the module. It contains: 8 | 9 | - The module path 10 | - The version of the Go language your project requires 11 | - Optionally, any external package dependencies your project has 12 | 13 | The module path is just the import path prefix for all packages within the module. Here's an example of a `go.mod` file: 14 | 15 | ```go 16 | module github.com/AmanuelCh/linkpreview 17 | 18 | go 1.22 19 | 20 | require github.com/google/examplepackage v1.3.0 21 | ``` 22 | 23 | Each module's path not only serves as an import path prefix for the packages within but *also indicates where the go command should look to download it*. For example, to download the module `golang.org/x/tools`, the go command would consult the repository located at [https://golang.org/x/tools](https://golang.org/x/tools). 24 | 25 | > An "import path" is a string used to import a package. A package's import path is its module path joined with its subdirectory within the module. For example, the module `github.com/google/go-cmp` contains a package in the directory `cmp/`. That package's import path is `github.com/google/go-cmp/cmp`. Packages in the standard library do not have a module path prefix. 26 | 27 | ## Do I Need to Put my Package on GitHub? 28 | 29 | You don't *need* to publish your code to a remote repository before you can build it. A module can be defined locally without belonging to a repository. However, it's a good habit to keep a copy of all your projects on a remote server, like GitHub. 30 | 31 | ## Go Path 32 | 33 | Your machine will contain many version control *repositories* (managed by Git, for example). Each repository contains one or more *packages*, but will typically be a single *module*. Each package consists of one or more *Go source files* in a single directory. The path to a package's directory determines its *import path* and where it can be downloaded from if you decide to host it on a remote version control system like Github or Gitlab. 34 | 35 | > The `$GOPATH` environment variable will be set by default somewhere on your machine (typically in the home directory, `~/go`). Since we will be working in the new "Go modules" setup, you *don't need to worry about that*. If you read something online about setting up your `GOPATH`, that documentation is probably out of date. These days you should *avoid* working in the `$GOPATH/src` directory. Again, that's the old way of doing things and can cause unexpected issues, so better to just avoid it. 36 | 37 | ## Get into your Workspace 38 | 39 | Navigate to a location on your machine where you want to store some code. For example, I store all my code in `~/workspace`, then organize it into subfolders based on the remote location. For example, 40 | 41 | > `~/workspace/github.com/AmanuelCh/linkpreview` = [https://github.com/AmanuelCh/linkpreview](https://github.com/AmanuelCh/linkpreview) 42 | 43 | That said, you can put your code wherever you want. -------------------------------------------------------------------------------- /src/data/setting-up-environment/packages.md: -------------------------------------------------------------------------------- 1 | # Packages 2 | 3 | Every Go program is made up of packages. You have probably noticed the `package main` at the top of the `hello world` program from the [Intro](/intro) section. A package named "main" has an entrypoint at the `main()` function. A `main` package is compiled into an executable program. A package by any other name is a "library package". Libraries have no entry point. Libraries simply export functionality that can be used by other packages. For example: 4 | 5 | ```go 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "math/rand" 11 | ) 12 | 13 | func main() { 14 | fmt.Println("My favorite number is", rand.Intn(10)) 15 | } 16 | ``` 17 | 18 | This program is an executable. It is a "main" package and *imports* from the `fmt` and `math/rand` library packages. 19 | 20 | ## Package Naming 21 | 22 | By *convention*, a package's name is the same as the last element of its import path. For instance, the `math/rand` package comprises files that begin with: 23 | 24 | ```go 25 | package rand 26 | ``` 27 | 28 | That said, package names aren't *required* to match their import path. For example, you could write a new package with the path `github.com/mailio/rand` and name the package `random`: 29 | 30 | ```go 31 | package random 32 | ``` 33 | 34 | While the above is possible, it is discouraged for the sake of consistency. 35 | 36 | ## One Package / Directory 37 | 38 | A directory of Go code can have **at most** one package. All `.go` files in a single directory must all belong to the same package. If they don't an error will be thrown by the compiler. This is true for main and library packages alike. 39 | -------------------------------------------------------------------------------- /src/data/setting-up-environment/wrapping-up.md: -------------------------------------------------------------------------------- 1 | # Wrapping Up 2 | 3 | Ready to write your first Go program on your local machine? We have covered how to do so. Here's a quick recap: 4 | 5 | ## Installation: The Gateway to Go 6 | 7 | Installing Go is a breeze. Here are a few ways to get it up and running: 8 | 9 | - Official Website: The official Go website ([https://golang.org/](https://golang.org/)) provides clear installation instructions for your operating system. It's the easiest and most reliable way to get started. 10 | - Package Managers (Mac & Linux): You can also install Go using package managers like `brew` (macOS), `apt` (Debian/Ubuntu), or `yum` (Red Hat/CentOS). These tools simplify the installation process. 11 | 12 | ### Essential Tools: Your Go Toolbox 13 | 14 | Once Go is installed, you'll want to familiarize yourself with some essential tools: 15 | 16 | - `go fmt`: This command automatically formats your Go code, ensuring consistent style and readability. 17 | - `go run`: Run your Go code directly from the command line. This is great for quick testing and experimentation. 18 | - `go build`: Compile your Go code into an executable file, allowing you to distribute and run your program on different machines. 19 | - `go get`: Install and manage Go packages and modules (which are collections of reusable code). 20 | 21 | ### A Package of Power: Modules and Packages 22 | 23 | Go's module system is a game-changer for managing dependencies. Modules are collections of Go packages that provide reusable functionality. Here's why they are so important: 24 | 25 | - Organization: Modules help organize your code into logical units, making it easier to manage and maintain large projects. 26 | - Dependency Management: Modules allow you to easily include and update libraries of code created by others, saving you time and effort. 27 | 28 | ### Your Coding Canvas: Text Editors and IDEs 29 | 30 | Go works well with various text editors and integrated development environments (IDEs). 31 | 32 | - VS Code: A highly popular and customizable editor with excellent Go extensions that provide features like syntax highlighting, code completion, and debugging. 33 | - GoLand: A powerful IDE specifically designed for Go development, providing comprehensive features like code navigation, testing, and refactoring. 34 | - Go Playground: A fantastic online environment where you can write and run Go code without any installation. It's a great place to experiment and learn. 35 | 36 | ### Ready to Code? 37 | 38 | You've now got your Go environment ready to go. Don't hesitate to explore these tools, experiment, and dive into the world of Go. From here, you can tackle variables, data types, and more. Happy coding! 39 | -------------------------------------------------------------------------------- /src/data/setting-up-environment/your-first-go-program.md: -------------------------------------------------------------------------------- 1 | # Your First Go Program 2 | 3 | Once inside your personal workspace, create a new directory and enter it: 4 | 5 | ```bash 6 | mkdir hellogo 7 | cd hellogo 8 | ``` 9 | 10 | Inside the directory declare your module's name: 11 | 12 | ```bash 13 | go mod init {REMOTE}/{USERNAME}/hellogo 14 | ``` 15 | 16 | Where `{REMOTE}` is your preferred remote source provider (i.e. `github.com`) and `{USERNAME}` is your Git username. If you don't use a remote provider yet, just use `example.com/username/hellogo`. Print your `go.mod` file: 17 | 18 | ```bash 19 | cat go.mod 20 | ``` 21 | 22 | Inside `hellogo`, create a new file called `main.go`. Conventionally, the file in the `main` package that contains the `main()` function is called `main.go`. Paste the following code into your file: 23 | 24 | ```go 25 | package main 26 | 27 | import "fmt" 28 | 29 | func main() { 30 | fmt.Println("hello world") 31 | } 32 | ``` 33 | 34 | Run the code 35 | 36 | ```bash 37 | go run main.go 38 | ``` 39 | 40 | The `go run` command is used to quickly compile and run a Go package. The compiled binary is *not* saved in your working directory. Use `go build` instead to compile production executables. I rarely use `go run` other than to quickly do some testing or debugging. 41 | 42 | ## Further Reading 43 | 44 | Execute `go help run` in your shell and read the instructions. -------------------------------------------------------------------------------- /src/data/slices/arrays.md: -------------------------------------------------------------------------------- 1 | # Arrays in Go 2 | 3 | Like most programming languages, Go has arrays. However, arrays are rarely used directly in Go. All elements in the array must be of the type that’s specified. There are a few declaration styles. In the first, you specify the size of the array and the type of the elements in the array: 4 | 5 | ```go 6 | var x [3]int 7 | ``` 8 | 9 | This creates an array of three ints. Since no values were specified, all of the elements (x[0], x[1], and x[2]) are initialized to the zero value for an int, which is (of course) 0. If you have initial values for the array, you specify them with an array literal: 10 | 11 | ```go 12 | var x = [3]int{10, 20, 30} 13 | ``` 14 | 15 | If you have a sparse array (an array where most elements are set to their zero value), you can specify only the indices with nonzero values in the array literal: 16 | 17 | ```go 18 | var x = [12]int{1, 5: 4, 6, 10: 100, 15} 19 | ``` 20 | 21 | This creates an array of 12 ints with the following values: [1, 0, 0, 0, 0, 4, 6, 0, 0, 0, 100, 15] 22 | 23 | ## Don't Use Arrays Directly 24 | 25 | We said that arrays in Go are rarely used explicitly. This is because they come with an unusual limitation: Go considers the size of the array to be part of the type of the array. This makes an array that’s declared to be [3]int a different type from an array that’s declared to be [4]int. This also means that you cannot use a variable to specify the size of an array, because types must be resolved at compile time, not at runtime. What’s more, you can’t use a type conversion to directly convert arrays of different sizes to identical types. Because you can’t convert arrays of different sizes into each other, you can’t write a function that works with arrays of any size and you can’t assign arrays of different sizes to the same variable. 26 | 27 | > Because of these restrictions, don’t use arrays unless you know the exact length you need ahead of time. For example, some of the cryptographic functions in the standard library return arrays because the sizes of checksums are defined as part of the algorithm. This is the exception, not the rule. This raises the question: why is such a limited feature in the language? The main reason arrays exist in Go is to provide the backing store for slices, which are one of the most useful features of Go. 28 | -------------------------------------------------------------------------------- /src/data/slices/len-and-cap-review.md: -------------------------------------------------------------------------------- 1 | # Len and Cap Review 2 | 3 | The length of a slice may be changed as long as it still fits within the limits of the underlying array; just assign it to a slice of itself. The *capacity* of a slice, accessible by the built-in function `cap`, reports the maximum length the slice may assume. Here is a function to append data to a slice. If the data exceeds the capacity, the slice is reallocated. The resulting slice is returned. The function uses the fact that `len` and `cap` are legal when applied to the `nil` slice, and return `0`. 4 | 5 | Referenced from [Effective Go](https://golang.org/doc/effective_go.html#slices) 6 | 7 | ```go 8 | func Append(slice, data []byte) []byte { 9 | l := len(slice) 10 | if l + len(data) > cap(slice) { // reallocate 11 | // Allocate double what's needed, for future growth. 12 | newSlice := make([]byte, (l+len(data))*2) 13 | // The copy function is predeclared and works for any slice type. 14 | copy(newSlice, slice) 15 | slice = newSlice 16 | } 17 | slice = slice[0:l+len(data)] 18 | copy(slice[l:], data) 19 | return slice 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /src/data/slices/make-function.md: -------------------------------------------------------------------------------- 1 | # Make Function 2 | 3 | Most of the time we don't need to think about the underlying array of a slice. We can create a new slice using the `make` function: 4 | 5 | ```go 6 | // func make([]T, len, cap) []T 7 | mySlice := make([]int, 5, 10) 8 | 9 | // the capacity argument is usually omitted and defaults to the length 10 | mySlice := make([]int, 5) 11 | ``` 12 | 13 | Slices created with `make` will be filled with the zero value of the type. If we want to create a slice with a specific set of values, we can use a slice literal: 14 | 15 | ```go 16 | mySlice := []string{"I", "love", "go"} 17 | ``` 18 | 19 | Note that the array brackets *do not* have a `3` in them. If they did, you'd have an *array* instead of a slice. 20 | 21 | ## Length 22 | 23 | The length of a slice is simply the number of elements it contains. It is accessed using the built-in `len()` function: 24 | 25 | ```go 26 | mySlice := []string{"I", "love", "go"} 27 | fmt.Println(len(mySlice)) // 3 28 | ``` 29 | 30 | ## Capacity 31 | 32 | The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice. It is accessed using the built-in `cap()` function: 33 | 34 | ```go 35 | mySlice := []string{"I", "love", "go"} 36 | fmt.Println(cap(mySlice)) // 3 37 | ``` 38 | 39 | Generally speaking, unless you're hyper-optimizing the memory usage of your program, you don't need to worry about the capacity of a slice because it will automatically grow as needed. 40 | -------------------------------------------------------------------------------- /src/data/slices/slice-gotcha.md: -------------------------------------------------------------------------------- 1 | # Tricky Slices 2 | 3 | The `append()` function changes the underlying array of its parameter AND returns a new slice. This means that using `append()` on anything other than itself is usually a BAD idea. 4 | 5 | ```go 6 | // dont do this! 7 | someSlice = append(otherSlice, element) 8 | ``` 9 | 10 | Take a look at these head-scratchers: 11 | 12 | ## Example 1: Works as expected 13 | 14 | ```go 15 | a := make([]int, 3) 16 | fmt.Println("len of a:", len(a)) 17 | // len of a: 3 18 | fmt.Println("cap of a:", cap(a)) 19 | // cap of a: 3 20 | fmt.Println("appending 4 to b from a") 21 | // appending 4 to b from a 22 | b := append(a, 4) 23 | fmt.Println("b:", b) 24 | // b: [0 0 0 4] 25 | fmt.Println("addr of b:", &b[0]) 26 | // addr of b: 0x44a0c0 27 | fmt.Println("appending 5 to c from a") 28 | // appending 5 to c from a 29 | c := append(a, 5) 30 | fmt.Println("addr of c:", &c[0]) 31 | // addr of c: 0x44a180 32 | fmt.Println("a:", a) 33 | // a: [0 0 0] 34 | fmt.Println("b:", b) 35 | // b: [0 0 0 4] 36 | fmt.Println("c:", c) 37 | // c: [0 0 0 5] 38 | ``` 39 | 40 | With slices `a`, `b`, and `c`, `4` and `5` seem to be appended as we would expect. We can even check the memory addresses and confirm that `b` and `c` point to different underlying arrays. 41 | 42 | ## Example 2: Something fishy 43 | 44 | ```go 45 | i := make([]int, 3, 8) 46 | fmt.Println("len of i:", len(i)) 47 | // len of i: 3 48 | fmt.Println("cap of i:", cap(i)) 49 | // cap of i: 8 50 | fmt.Println("appending 4 to j from i") 51 | // appending 4 to j from i 52 | j := append(i, 4) 53 | fmt.Println("j:", j) 54 | // j: [0 0 0 4] 55 | fmt.Println("addr of j:", &j[0]) 56 | // addr of j: 0x454000 57 | fmt.Println("appending 5 to g from i") 58 | // appending 5 to g from i 59 | g := append(i, 5) 60 | fmt.Println("addr of g:", &g[0]) 61 | // addr of g: 0x454000 62 | fmt.Println("i:", i) 63 | // i: [0 0 0] 64 | fmt.Println("j:", j) 65 | // j: [0 0 0 5] 66 | fmt.Println("g:", g) 67 | // g: [0 0 0 5] 68 | ``` 69 | 70 | In this example however, when `5` is appended to `g` it overwrites `j`'s fourth index because `j` and `g` point to the *same underlying array*. The `append()` function only creates a new array when there isn't any capacity left. We created `i` with a length of 3 and a capactiy of 8, which means we can append `5` items before a new array is automatically allocated. Again, to avoid bugs like this, you should always use the `append` function on the same slice the result is assigned to: 71 | 72 | ```go 73 | mySlice := []int{1, 2, 3} 74 | mySlice = append(mySlice, 4) 75 | ``` 76 | -------------------------------------------------------------------------------- /src/data/slices/slices-in-go.md: -------------------------------------------------------------------------------- 1 | # Slices in Go 2 | 3 | *99 times out of 100* you will use a slice instead of an array when working with ordered lists. Arrays are fixed in size. Once you make an array like `[10]int` you can't add an 11th element. A slice is a *dynamically-sized*, *flexible* view of the elements of an array. Slices **always** have an underlying array, though it isn't always specified explicitly. To explicitly create a slice on top of an array we can do: 4 | 5 | ```go 6 | primes := [6]int{2, 3, 5, 7, 11, 13} 7 | mySlice := primes[1:4] 8 | // mySlice = {3, 5, 7} 9 | ``` 10 | 11 | The syntax is: 12 | 13 | ```go 14 | arrayname[lowIndex:highIndex] 15 | arrayname[lowIndex:] 16 | arrayname[:highIndex] 17 | arrayname[:] 18 | ``` 19 | 20 | Where `lowIndex` is inclusive and `highIndex` is exclusive. Either `lowIndex` or `highIndex` or both can be omitted to use the entire array on that side. 21 | 22 | > What makes slices so useful is that you can grow slices as needed. This is because the length of a slice is not part of its type. This removes the biggest limitations of arrays and allows you to write a single function that processes slices of any size. 23 | -------------------------------------------------------------------------------- /src/data/slices/slices-review.md: -------------------------------------------------------------------------------- 1 | # Slices Review 2 | 3 | Slices wrap arrays to give a more general, powerful, and convenient interface to sequences of data. Except for items with explicit dimensions such as transformation matrices, most array programming in Go is done with slices rather than simple arrays. 4 | 5 | ### Slices hold references 6 | 7 | Slices hold references to an underlying array, and if you assign one slice to another, both refer to the **same** array. If a function takes a slice argument, changes it makes to the elements of the slice *will be visible to the caller*, analogous to passing a pointer to the underlying array. A Read function can therefore accept a slice argument rather than a pointer and a count; the length within the slice sets an upper limit of how much data to read. Here is the signature of the [Read()](https://pkg.go.dev/os#File.Read) method of the `File` type in package `os`: 8 | 9 | Referenced from [Effective Go](https://golang.org/doc/effective_go.html#slices) 10 | 11 | ```go 12 | func (f *File) Read(buf []byte) (n int, err error) 13 | ``` 14 | 15 | ### Can we compare slices ? 16 | 17 | It is a compile-time error to use `==` to see if two slices are identical or `!=` to see if they are different. The only thing you can compare a slice with using `==` is `nil`: 18 | 19 | ```go 20 | var x []int //x is assigned the zero value for a slice 21 | 22 | fmt.Println(x == nil) // prints true 23 | ``` 24 | 25 | Since Go 1.21, the slices package in the standard library includes two functions to compare slices. The `slices.Equal` function takes in two slices and returns true if the slices are the same length, and all of the elements are equal. It requires the elements of the slice to be comparable. The other function, `slices.EqualFunc`, lets you pass in a function to determine equality and does not require the slice elements to be comparable. 26 | -------------------------------------------------------------------------------- /src/data/slices/variadic-functions.md: -------------------------------------------------------------------------------- 1 | # Variadic Functions 2 | 3 | Many functions, especially those in the standard library, can take an arbitrary number of *final* arguments. This is accomplished by using the "`...`" syntax in the function signature. A variadic function receives the variadic arguments as a slice. 4 | 5 | ```go 6 | func sum(nums ...int) int { 7 | // nums is just a slice 8 | for i := 0; i < len(nums); i++{ 9 | num := nums[i] 10 | } 11 | } 12 | 13 | func main() { 14 | total := sum(1, 2, 3) 15 | fmt.Println(total) 16 | // prints "6" 17 | } 18 | ``` 19 | 20 | The familiar [fmt.Println()](https://pkg.go.dev/fmt#Println) and [fmt.Sprintf()](https://pkg.go.dev/fmt#Sprintf) are variadic! `fmt.Println()` prints each element with space [delimiters](https://www.dictionary.com/browse/delimited) and a newline at the end. 21 | 22 | ```go 23 | func Println(a ...interface{}) (n int, err error) 24 | ``` 25 | 26 | ## Spread Operator 27 | 28 | The spread operator allows us to pass a slice *into* a variadic function. The spread operator consists of three dots following the slice in the function call. 29 | 30 | ```go 31 | func printStrings(strings ...string) { 32 | for i := 0; i < len(strings); i++ { 33 | fmt.Println(strings[i]) 34 | } 35 | } 36 | 37 | func main() { 38 | names := []string{"bob", "sue", "alice"} 39 | printStrings(names...) 40 | } 41 | ``` 42 | 43 | ## Append 44 | 45 | The built-in append function is used to dynamically add elements to a slice: 46 | 47 | ```go 48 | func append(slice []Type, elems ...Type) []Type 49 | ``` 50 | 51 | If the underlying array is not large enough, `append()` will create a new underlying array and point the slice to it. Notice that `append()` is variadic, the following are all valid: 52 | 53 | ```go 54 | slice = append(slice, oneThing) 55 | slice = append(slice, firstThing, secondThing) 56 | slice = append(slice, anotherSlice...) 57 | ``` 58 | 59 | ## Slice of Slices 60 | 61 | Slices can hold other slices, effectively creating a [matrix](https://en.wikipedia.org/wiki/Matrix_(mathematics)), or a 2D slice. 62 | 63 | ```go 64 | rows := [][]int{} 65 | ``` 66 | 67 | ## Range 68 | 69 | Go provides syntactic sugar to iterate easily over elements of a slice: 70 | 71 | ```go 72 | for INDEX, ELEMENT := range SLICE { 73 | } 74 | ``` 75 | 76 | For example: 77 | 78 | ```go 79 | fruits := []string{"apple", "banana", "grape"} 80 | for i, fruit := range fruits { 81 | fmt.Println(i, fruit) 82 | } 83 | // 0 apple 84 | // 1 banana 85 | // 2 grape 86 | ``` 87 | -------------------------------------------------------------------------------- /src/data/slices/wrapping-up.md: -------------------------------------------------------------------------------- 1 | # Wrapping Up 2 | 3 | ## Slicing and Dicing Your Data 4 | 5 | Arrays and slices are fundamental data structures in Go, providing flexible ways to store and manipulate collections of data. 6 | 7 | ### Arrays: Fixed-Size Collections 8 | 9 | Arrays are fixed-size collections that hold elements of the same data type. They are declared with a specific length, and you cannot change that length after creation. 10 | 11 | ```go 12 | var numbers [5]int // An array of 5 integers 13 | numbers[0] = 1 14 | numbers[1] = 2 15 | numbers[2] = 3 16 | numbers[3] = 4 17 | numbers[4] = 5 18 | ``` 19 | 20 | ### Slices: Dynamic and Flexible 21 | 22 | Slices are dynamic, resizable segments of arrays. They provide a more flexible way to work with collections, allowing you to add, remove, and resize elements on the fly. 23 | 24 | ```go 25 | letters := []string{"a", "b", "c"} // A slice of strings 26 | ``` 27 | 28 | ### The `make` Function: Creating Slices 29 | 30 | The `make` function is used to create slices, specifying their initial length and capacity. 31 | 32 | ```go 33 | numbers := make([]int, 5, 10) // Create a slice with length 5 and capacity 10 34 | ``` 35 | 36 | - Length: The number of elements currently in the slice. 37 | - Capacity: The total number of elements the slice can hold without reallocation. 38 | 39 | ### Variadic Functions: Handling Variable Number of Arguments 40 | 41 | Variadic functions accept a variable number of arguments of the same type. This is useful for functions that need to work with an unknown number of inputs. 42 | 43 | ```go 44 | func sum(numbers ...int) int { 45 | total := 0 46 | for _, number := range numbers { 47 | total += number 48 | } 49 | return total 50 | } 51 | 52 | result := sum(1, 2, 3, 4, 5) // Pass any number of arguments 53 | ``` 54 | 55 | ### `len` and `cap`: Getting Length and Capacity 56 | 57 | The `len` function returns the length of a slice, and the `cap` function returns its capacity. 58 | 59 | ```go 60 | numbers := make([]int, 5, 10) 61 | fmt.Println(len(numbers)) // Output: 5 62 | fmt.Println(cap(numbers)) // Output: 10 63 | ``` 64 | 65 | ### Slice Gotchas: Avoiding Common Pitfalls 66 | 67 | - Slices are References: Slices are references to underlying arrays. Changes made to a slice affect the underlying array. 68 | - Slice Resizing: When a slice grows beyond its capacity, Go will automatically reallocate memory, potentially copying the data to a new location. 69 | - Slice Appending: The `append` function adds elements to a slice, resizing it as needed. 70 | -------------------------------------------------------------------------------- /src/data/structs/anonymous-structs.md: -------------------------------------------------------------------------------- 1 | # Anonymous Structs 2 | 3 | An anonymous struct is just like a normal struct, but it is defined without a name and therefore cannot be referenced elsewhere in the code. To create an anonymous struct, just instantiate the instance immediately using a second pair of brackets after declaring the type: 4 | 5 | ```go 6 | myCar := struct { 7 | Make string 8 | Model string 9 | } { 10 | Make: "tesla", 11 | Model: "model 3" 12 | } 13 | ``` 14 | 15 | You can even nest anonymous structs as fields within other structs: 16 | 17 | ```go 18 | type car struct { 19 | Make string 20 | Model string 21 | Height int 22 | Width int 23 | // Wheel is a field containing an anonymous struct 24 | Wheel struct { 25 | Radius int 26 | Material string 27 | } 28 | } 29 | ``` 30 | 31 | ## When Should you use an Anonymous struct? 32 | 33 | In general, *prefer named structs*. Named structs make it easier to read and understand your code, and they have the nice side-effect of being reusable. I sometimes use anonymous structs when I *know* I won't ever need to use a struct again. For example, sometimes I'll use one to create the shape of some JSON data in HTTP handlers. If a struct is only meant to be used once, then it makes sense to declare it in such a way that developers down the road won’t be tempted to accidentally use it again. You can read more about [anonymous structs here](https://www.willem.dev/articles/anonymous-structs/) if you're curious. 34 | -------------------------------------------------------------------------------- /src/data/structs/embedded-structs.md: -------------------------------------------------------------------------------- 1 | # Embedded Structs 2 | 3 | Go is not an [object-oriented](https://en.wikipedia.org/wiki/Object-oriented_programming) language. However, embedded structs provide a kind of *data-only* inheritance that can be useful at times. Keep in mind, Go doesn't support classes or inheritance in the complete sense, embedded structs are just a way to elevate and share fields between struct definitions. 4 | 5 | ```go 6 | type car struct { 7 | make string 8 | model string 9 | } 10 | 11 | type truck struct { 12 | // "car" is embedded, so the definition of a "truck" now also additionally contains all of the fields of the car struct 13 | car 14 | bedSize int 15 | } 16 | ``` 17 | 18 | ## Embedded vs Nested Struct 19 | 20 | - An embedded struct's fields are accessed at the top level, unlike nested structs. 21 | - Promoted fields can be accessed like normal fields except that they can't be used in [composite literals](https://golang.org/ref/spec#Composite_literals) 22 | 23 | ```go 24 | lanesTruck := truck{ 25 | bedSize: 10, 26 | car: car{ 27 | make: "toyota", 28 | model: "camry", 29 | }, 30 | } 31 | 32 | fmt.Println(lanesTruck.bedSize) 33 | 34 | // embedded fields promoted to the top-level 35 | // instead of lanesTruck.car.make 36 | fmt.Println(lanesTruck.make) 37 | fmt.Println(lanesTruck.model) 38 | ``` 39 | -------------------------------------------------------------------------------- /src/data/structs/nested-structs.md: -------------------------------------------------------------------------------- 1 | # Nested structs in Go 2 | 3 | Structs can be nested to represent more complex entities: 4 | 5 | ```go 6 | type car struct { 7 | Make string 8 | Model string 9 | Height int 10 | Width int 11 | FrontWheel Wheel 12 | BackWheel Wheel 13 | } 14 | 15 | type Wheel struct { 16 | Radius int 17 | Material string 18 | } 19 | ``` 20 | 21 | The fields of a struct can be accessed using the dot `.` operator. 22 | 23 | ```go 24 | myCar := car{} 25 | myCar.FrontWheel.Radius = 5 26 | ``` 27 | -------------------------------------------------------------------------------- /src/data/structs/struct-methods.md: -------------------------------------------------------------------------------- 1 | # Struct Methods in Go 2 | 3 | While Go is **not** object-oriented, it does support methods that can be defined on structs. Methods are just functions that have a receiver. A receiver is a special parameter that syntactically goes *before* the name of the function. 4 | 5 | ```go 6 | type rect struct { 7 | width int 8 | height int 9 | } 10 | 11 | // area has a receiver of (r rect) 12 | func (r rect) area() int { 13 | return r.width * r.height 14 | } 15 | 16 | r := rect{ 17 | width: 5, 18 | height: 10, 19 | } 20 | 21 | fmt.Println(r.area()) 22 | // prints 50 23 | ``` 24 | 25 | A receiver is just a special kind of function parameter. Receivers are important because they will allow us to define interfaces that our structs (and other types) can implement. 26 | -------------------------------------------------------------------------------- /src/data/structs/structs-in-go.md: -------------------------------------------------------------------------------- 1 | # Structs in Go 2 | 3 | We use `structs` in Go to represent structured data. A struct type is defined with the keyword type, the name of the struct type, the keyword struct, and a pair of braces ({}). Within the braces, you list the fields in the struct. Just as you put the variable name first and the variable type second in a var declaration, you put the struct field name first and the struct field type second. It's often convenient to group different types of variables together. For example, if we want to represent a car we could do the following: 4 | 5 | ```go 6 | type car struct { 7 | Make string 8 | Model string 9 | Height int 10 | Width int 11 | } 12 | ``` 13 | 14 | This creates a new struct type called `car`. All cars have a `Make`, `Model`, `Height` and `Width`. In Go, you will often use a `struct` to represent information that you would have used a dictionary for in Python, or an object literal for in JavaScript. You can define a struct type inside or outside of a function. A struct type that’s defined within a function can be used only within that function. 15 | 16 | > If you already know an object-oriented language, you might be wondering about the difference between classes and structs. The difference is simple: Go doesn’t have classes, because it doesn’t have inheritance. This doesn’t mean that Go doesn’t have some of the features of object-oriented languages it just does things a little differently. 17 | -------------------------------------------------------------------------------- /src/data/variables-and-types/basic-types.md: -------------------------------------------------------------------------------- 1 | # Basic Types 2 | 3 | Go's basic variable types are: 4 | 5 | ```go 6 | bool 7 | 8 | string 9 | 10 | int int8 int16 int32 int64 11 | uint uint8 uint16 uint32 uint64 uintptr 12 | 13 | byte // alias for uint8 14 | 15 | rune // alias for int32 16 | // represents a Unicode code point 17 | 18 | float32 float64 19 | 20 | complex64 complex128 21 | ``` 22 | 23 | `String`s and `int`s should be fairly self-explanatory. A `bool` is a boolean variable, meaning it has a value of `true` or `false`. The [floating point](https://en.wikipedia.org/wiki/Floating-point_arithmetic) types (`float32` and `float64`) are used for numbers that are not integers -- that is, they have digits to the right of the decimal place, such as `3.14159`. The `float32` type uses 32 bits of precision, while the `float64` type uses 64 bits to be able to more precisely store more digits. Don't worry too much about the intricacies of the other types for now. 24 | 25 | ## Declaring a Variable 26 | 27 | Variables are declared using the `var` keyword. For example, to declare a variable called `number` of type `int`, you would write: 28 | 29 | ```go 30 | var number int 31 | ``` 32 | 33 | To declare a variable called `pi` to be of type `float64` with a value of `3.14159`, you would write: 34 | 35 | ```go 36 | var pi float64 = 3.14159 37 | ``` 38 | 39 | The value of an initialized variable with no assignment will be its [zero value](https://tour.golang.org/basics/12). 40 | 41 | ## Short Variable Declaration 42 | 43 | Inside a function (even the main function), the `:=` short assignment statement can be used in place of a `var` declaration. 44 | 45 | ```go 46 | var empty string 47 | ``` 48 | 49 | Is the same as 50 | 51 | ```go 52 | empty := "" 53 | ``` 54 | 55 | The `:=` operator infers the type of the new variable based on the value. 56 | 57 | ```go 58 | numCars := 10 // inferred to be an integer 59 | 60 | temperature := 0.0 // temperature is inferred to be a floating point value because it has a decimal point 61 | 62 | var isFunny = true // isFunny is inferred to be a boolean 63 | ``` 64 | 65 | Outside of a function (in the [global/package scope](https://dave.cheney.net/2017/06/11/go-without-package-scoped-variables)), every statement begins with a keyword (`var`, `func`, and so on) and so the `:=` construct is not available. -------------------------------------------------------------------------------- /src/data/variables-and-types/conditionals.md: -------------------------------------------------------------------------------- 1 | # Conditionals 2 | 3 | `if` statements in Go don't use parentheses around the condition: 4 | 5 | ```go 6 | if height > 4 { 7 | fmt.Println("You are tall enough!") 8 | } 9 | ``` 10 | 11 | `else if` and `else` are supported as you would expect: 12 | 13 | ```go 14 | if height > 6 { 15 | fmt.Println("You are super tall!") 16 | } else if height > 4 { 17 | fmt.Println("You are tall enough!") 18 | } else { 19 | fmt.Println("You are not tall enough!") 20 | } 21 | ``` 22 | 23 | Here are some of the comparison operators in Go: 24 | 25 | ```go 26 | - `==` // equal to 27 | - `!=` // not equal to 28 | - `<` // less than 29 | - `>` // greater than 30 | - `<=` // less than or equal to 31 | - `>=` // greater than or equal to 32 | ``` 33 | 34 | ## The Initial Statement of an if Block 35 | 36 | An `if` conditional can have an "initial" statement. The variable(s) created in the initial statement are *only* defined within the scope of the `if` body. 37 | 38 | ```go 39 | if INITIAL_STATEMENT; CONDITION { 40 | } 41 | ``` 42 | 43 | ### Why would I use this? 44 | 45 | This is just some syntactic sugar that Go offers to shorten up code in some cases. For example, instead of writing: 46 | 47 | ```go 48 | length := getLength(email) 49 | if length < 1 { 50 | fmt.Println("Email is invalid") 51 | } 52 | ``` 53 | 54 | We can do: 55 | 56 | ```go 57 | if length := getLength(email); length < 1 { 58 | fmt.Println("Email is invalid") 59 | } 60 | ``` 61 | 62 | Not only is this code a bit shorter, but it also removes `length` from the parent scope, which is convenient because we don't need it there - we only need access to it while checking a condition. 63 | -------------------------------------------------------------------------------- /src/data/variables-and-types/constants.md: -------------------------------------------------------------------------------- 1 | # Constants 2 | 3 | Constants are declared like variables but use the `const` keyword. Constants can't use the `:=` short declaration syntax. Constants can be character, string, boolean, or numeric values. They *can not* be more complex types like slices, maps and structs, which are types we will explain later. As the name implies, the value of a constant can't be changed after it has been declared. 4 | 5 | ## Computed Constants 6 | 7 | Constants must be known at compile time. More often than not they will be declared with a static value: 8 | 9 | ```go 10 | const myInt = 15 11 | ``` 12 | 13 | However, constants *can be computed* so long as the computation can happen at *compile time*. For example, this is valid: 14 | 15 | ```go 16 | const firstName = "John" 17 | const lastName = "Doe" 18 | const fullName = firstName + " " + lastName // John Doe 19 | ``` 20 | 21 | That said, you *cannot* declare a constant that can only be computed at run-time. 22 | -------------------------------------------------------------------------------- /src/data/variables-and-types/formatting-strings.md: -------------------------------------------------------------------------------- 1 | # Formatting Strings in Go 2 | 3 | Go follows the [printf tradition](https://cplusplus.com/reference/cstdio/printf/) from the C language. Honestly speaking, string formatting/interpolation in Go is currently *less* elegant than JavaScript and Python 4 | 5 | - [fmt.Printf](https://pkg.go.dev/fmt#Printf) - Prints a formatted string to [standard output](https://stackoverflow.com/questions/3385201/confused-about-stdin-stdout-and-stderr). 6 | - [fmt.Sprintf()](https://pkg.go.dev/fmt#Sprintf) - Returns the formatted string 7 | 8 | ## Examples 9 | 10 | ### %v - Interpolate the default representation 11 | 12 | The `%v` variant prints the Go syntax representation of a value. You can usually use this if you're unsure what else to use. That said, it's better to use the type-specific variant if you can. 13 | 14 | ```go 15 | fmt.Printf("I am %v years old", 10) 16 | // I am 10 years old 17 | 18 | fmt.Printf("I am %v years old", "way too many") 19 | // I am way too many years old 20 | ``` 21 | 22 | ### `%s` - Interpolate a string 23 | 24 | ```go 25 | fmt.Printf("I am %s years old", "way too many") 26 | // I am way too many years old 27 | ``` 28 | 29 | ### `%d` - Interpolate an integer in decimal form 30 | 31 | ```go 32 | fmt.Printf("I am %d years old", 10) 33 | // I am 10 years old 34 | ``` 35 | 36 | ### `%f` - Interpolate a decimal 37 | 38 | ```go 39 | fmt.Printf("I am %f years old", 10.523) 40 | // I am 10.523000 years old 41 | 42 | // The ".2" rounds the number to 2 decimal places 43 | fmt.Printf("I am %.2f years old", 10.523) 44 | // I am 10.53 years old 45 | ``` 46 | 47 | If you're interested in all the formatting options, feel free to take a look at the `fmt` package's [docs here](https://pkg.go.dev/fmt#hdr-Printing). -------------------------------------------------------------------------------- /src/data/variables-and-types/type-inference.md: -------------------------------------------------------------------------------- 1 | # Type Inference 2 | 3 | To declare a variable without specifying an explicit type (either by using the `:=` syntax or `var = expression` syntax), the variable's type is *inferred* from the value on the right hand side. When the right hand side of the declaration is typed, the new variable is of that same type: 4 | 5 | ```go 6 | var i int 7 | j := i // j is also an int 8 | ``` 9 | 10 | However, when the right hand side is a literal value (an untyped numeric constant like `42` or `3.14`), the new variable will be an `int`, `float64`, or `complex128` depending on its precision: 11 | 12 | ```go 13 | i := 42 // int 14 | f := 3.14 // float64 15 | g := 0.867 + 0.5i // complex128 16 | ``` 17 | 18 | ## Same Line Declarations 19 | 20 | We are able to declare multiple variables on the same line: 21 | 22 | ```go 23 | mileage, company := 80276, "Tesla" 24 | 25 | // is the same as 26 | 27 | mileage := 80276 28 | company := "Tesla" 29 | ``` 30 | -------------------------------------------------------------------------------- /src/data/variables-and-types/type-sizes.md: -------------------------------------------------------------------------------- 1 | # Type Sizes 2 | 3 | Ints, [uints](https://www.cs.utah.edu/~germain/PPS/Topics/unsigned_integer.html#:~:text=Unsigned%20Integers,negative%20(zero%20or%20positive).), [floats](https://techterms.com/definition/floatingpoint), and [complex](https://www.cloudhadoop.com/2018/12/golang-tutorials-complex-types-numbers.html#:~:text=Golang%20Complex%20Type%20Numbers,complex%20number%20is%2012.8i.) numbers all have type sizes. 4 | 5 | ```go 6 | int int8 int16 int32 int64 // whole numbers 7 | 8 | uint uint8 uint16 uint32 uint64 uintptr // positive whole numbers 9 | 10 | float32 float64 // decimal numbers 11 | 12 | complex64 complex128 // imaginary numbers (rare) 13 | ``` 14 | 15 | The size (8, 16, 32, 64, 128, etc) indicates how many bits in memory will be used to store the variable. The default `int` and `uint` types are just aliases that refer to their respective 32 or 64 bit sizes depending on the environment of the user. The standard sizes that should be used unless the developer has a specific need are: 16 | 17 | - `int` 18 | - `uint` 19 | - `float64` 20 | - `complex128` 21 | 22 | Some types can be converted the following way: 23 | 24 | ```go 25 | temperatureInt := 88 26 | temperatureFloat := float64(temperatureInt) 27 | ``` 28 | 29 | Casting a float to an integer in this way [truncates](https://techterms.com/definition/truncate) the floating point portion. 30 | 31 | ## Which Type Should I Use? 32 | 33 | With so many types for what is essentially just a number, developers coming from languages that only have one kind of `Number` type (like JavaScript) may find the choices daunting. 34 | 35 | ### Prefer "default" types 36 | 37 | A problem arises when we have a `uint16`, and the function we are trying to pass it into takes an `int`. We're forced to write code riddled with type casts like `int(myUint16)`. This style of development can be slow and annoying to read. When Go developers stray from the “default” type for any given type family, the code can get messy quickly. Unless you have a good reason to, stick to the following types: 38 | 39 | - `bool` 40 | - `string` 41 | - `int` 42 | - `uint32` 43 | - `byte` 44 | - `rune` 45 | - `float64` 46 | - `complex128` 47 | 48 | ### When should I use a more specific type? 49 | 50 | When you're super concerned about performance and memory usage. That’s about it. The only reason to deviate from the defaults is to squeeze out every last bit of performance when you are writing an application that is resource-constrained. (Or, in the special case of `uint64`, you need an absurd range of unsigned integers). 51 | -------------------------------------------------------------------------------- /src/data/variables-and-types/wrapping-up.md: -------------------------------------------------------------------------------- 1 | # Wrapping Up 2 | 3 | ## Building Blocks of Data: Variables and Types in Go 4 | 5 | In the world of programming, data is king. Go provides a set of powerful building blocks for representing and manipulating data, known as variables and types. Let's explore how to create and use these crucial elements: 6 | 7 | ### Fundamental Types: The Foundation of Data 8 | 9 | Go offers a variety of basic data types to represent different kinds of information. Here are some of the most commonly used ones: 10 | 11 | - `int`: Integer numbers (e.g., 10, -5, 0). 12 | - `float64`: Floating-point numbers (e.g., 3.14, 2.718). 13 | - `string`: Textual data (e.g., "Hello", "World!"). 14 | - `bool`: Boolean values (true or false). 15 | 16 | ### Declaring Variables: Giving Data a Name 17 | 18 | In Go, you declare variables using the `var` keyword. Let's see how it works: 19 | 20 | ```go 21 | var name string = "Alice" // Declare a string variable named "name" and assign the value "Alice" 22 | var age int = 30 // Declare an integer variable named "age" and assign the value 30 23 | ``` 24 | 25 | ### Type Inference: Go's Smart Shortcut 26 | 27 | Go often infers the type of a variable based on the value assigned to it. This is known as type inference: 28 | 29 | ```go 30 | name := "Bob" // The type of "name" is inferred to be "string" 31 | age := 25 // The type of "age" is inferred to be "int" 32 | ``` 33 | 34 | ### Constants: Unchanging Values 35 | 36 | Sometimes you need values that stay the same throughout your program. Constants are declared using the `const` keyword: 37 | 38 | ```go 39 | const PI float64 = 3.14159 // Define a constant named "PI" with the value 3.14159 40 | ``` 41 | 42 | ### Type Sizes: Understanding Memory Usage 43 | 44 | Go provides information about the size of its data types. This helps optimize memory usage and understand how much space your data will occupy. You can check type sizes using the `unsafe` package, but generally, it's best to rely on the default type sizes provided by Go. 45 | 46 | ### Making Decisions: Conditionals and `if` Statements 47 | 48 | You can control the flow of your program using conditional statements (like `if` statements): 49 | 50 | ```go 51 | if age >= 18 { 52 | fmt.Println("You are an adult.") 53 | } else { 54 | fmt.Println("You are not an adult yet.") 55 | } 56 | ``` 57 | 58 | ### Formatting Strings: Making Your Output Elegant 59 | 60 | Go provides a powerful formatting mechanism for strings: 61 | 62 | ```go 63 | name := "Alice" 64 | age := 30 65 | message := fmt.Sprintf("My name is %s, and I am %d years old.", name, age) 66 | fmt.Println(message) // Output: "My name is Alice, and I am 30 years old." 67 | ``` 68 | 69 | Now you have a grasp of the fundamental building blocks of Go's data handling system! In the next steps, you'll explore how to work with functions, data structures, and more. With this foundation, you're well on your way to building sophisticated Go programs. Happy coding! 70 | -------------------------------------------------------------------------------- /src/hooks/useKey.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | export function useKey(key: string, action: () => void) { 4 | useEffect( 5 | function () { 6 | function callback(e: KeyboardEvent) { 7 | if (e.code.toLowerCase() === key.toLowerCase()) { 8 | action(); 9 | } 10 | } 11 | 12 | document.addEventListener('keydown', callback); 13 | 14 | return function () { 15 | document.removeEventListener('keydown', callback); 16 | }; 17 | }, 18 | [action, key] 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { HelmetProvider } from 'react-helmet-async'; 4 | import App from './App.tsx'; 5 | import './index.css'; 6 | 7 | ReactDOM.createRoot(document.getElementById('root')!).render( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /src/utils/capitalizedWord.ts: -------------------------------------------------------------------------------- 1 | // removes unnecessary hyphens and capitalizes words 2 | export function capitalizeWords(str: string) { 3 | return str 4 | ?.replaceAll('-', ' ') 5 | .split(' ') 6 | .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) 7 | .join(' '); 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/checkRoute.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { categories } from './lists'; 3 | import { useNavigate, useParams } from 'react-router-dom'; 4 | 5 | function checkRoute(exercises?: string[]) { 6 | const { category } = useParams(); 7 | const navigate = useNavigate(); 8 | 9 | useEffect(() => { 10 | // if user navigated to a topic page 11 | if (category) { 12 | // check if category exists defined by us 13 | const isCategoryExist = categories.some( 14 | (prevCategory) => 15 | prevCategory.link.slice(1).toLowerCase() === category?.toLowerCase() 16 | ); 17 | 18 | // navigate to error page if user entered invalid category (the check is necessary because they might enter a URL manually and be invalid) 19 | if (!isCategoryExist) { 20 | console.log('Category does not exist:', category); 21 | navigate('/error'); 22 | } 23 | 24 | return; 25 | } 26 | 27 | // if single exercise page isn't one of the three type, navigate to error page 28 | if ( 29 | !exercises?.some( 30 | (exercise) => 31 | exercise === 'beginner' || 32 | exercise === 'intermediate' || 33 | exercise === 'advanced' 34 | ) 35 | ) { 36 | navigate('/error'); 37 | } 38 | }, [category, exercises, navigate]); 39 | } 40 | 41 | export default checkRoute; 42 | -------------------------------------------------------------------------------- /src/utils/cn.ts: -------------------------------------------------------------------------------- 1 | import { ClassValue, clsx } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | declare module '*.md'; 3 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], 4 | darkMode: 'class', 5 | theme: { 6 | extend: { 7 | fontFamily: { 8 | InterMedium: ['InterMedium', 'sans-serif'], 9 | InterBold: ['InterBold', 'sans-serif'], 10 | }, 11 | }, 12 | }, 13 | plugins: [], 14 | }; 15 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 5 | "target": "ES2020", 6 | "useDefineForClassFields": true, 7 | "lib": ["ES2021", "DOM", "DOM.Iterable"], 8 | "module": "ESNext", 9 | "skipLibCheck": true, 10 | 11 | /* Bundler mode */ 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "moduleDetection": "force", 17 | "noEmit": true, 18 | "jsx": "react-jsx", 19 | 20 | /* Linting */ 21 | "strict": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "noFallthroughCasesInSwitch": true 25 | }, 26 | "include": ["src"] 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.app.json" 6 | }, 7 | { 8 | "path": "./tsconfig.node.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 5 | "skipLibCheck": true, 6 | "module": "ESNext", 7 | "moduleResolution": "bundler", 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "noEmit": true 11 | }, 12 | "include": ["vite.config.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [ 7 | react(), 8 | // Custom plugin to load markdown files 9 | { 10 | name: 'markdown-loader', 11 | transform(code, id) { 12 | if (id.slice(-3) === '.md') { 13 | // For .md files, get the raw content 14 | return `export default ${JSON.stringify(code)};`; 15 | } 16 | }, 17 | }, 18 | ], 19 | }); 20 | --------------------------------------------------------------------------------