5. Публикация проекта на GitHub.
Инициализация git-репозитория — cоздайте локальный репозиторий Git:
Игнорирование файлов — cоздайте .gitignore файл, добавив туда стандартные исключения:
Или используйте готовый шаблон .gitignore: https://github.com/github/gitignore/blob/master/Go.gitignore
Добавьте и закоммитте файлы:
Создайте удаленный репозиторий на GitHub — залогиньтесь на GitHub и создайте новый пустой репозиторий с названием, соответствующим проекту.
Затем свяжите ваш локальный репозиторий с удаленным:
Отправляем наш первый коммит на сервер:
Контейнеризация и развертывание.
Создаем Dockerfile для контейнера нашего приложения:
Собираем образ и запускаем контейнер:
Вот основные шаги для создания проекта на Go и его публикации на GitHub:
— установка Go и настройка рабочего пространства;
— использование современной организации каталогов и модулей;
— применение хорошей практики написания кода и тестирования;
— организовали процесса сборки и тестирование через Makefile;
— размещение проекта на GitHub и настройка конвейера доставки через Docker.
Следование данным рекомендациям позволит поддерживать качественный и легко поддерживаемый проект.
Инициализация git-репозитория — cоздайте локальный репозиторий Git:
git init
Игнорирование файлов — cоздайте .gitignore файл, добавив туда стандартные исключения:
echo ".DS_Store
bin/
vendor/" > .gitignore
Или используйте готовый шаблон .gitignore: https://github.com/github/gitignore/blob/master/Go.gitignore
Добавьте и закоммитте файлы:
git add .
git commit -m "Initial project setup"
Создайте удаленный репозиторий на GitHub — залогиньтесь на GitHub и создайте новый пустой репозиторий с названием, соответствующим проекту.
Затем свяжите ваш локальный репозиторий с удаленным:
git remote add origin <url вашего репозитория на GitHub>
Отправляем наш первый коммит на сервер:
git push -u origin master
Контейнеризация и развертывание.
Создаем Dockerfile для контейнера нашего приложения:
# Dockerfile
FROM golang:alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/app .
EXPOSE 8080
CMD ["./app"]
Собираем образ и запускаем контейнер:
docker build -t my-go-app .
docker run -p 8080:8080 my-go-app
Вот основные шаги для создания проекта на Go и его публикации на GitHub:
— установка Go и настройка рабочего пространства;
— использование современной организации каталогов и модулей;
— применение хорошей практики написания кода и тестирования;
— организовали процесса сборки и тестирование через Makefile;
— размещение проекта на GitHub и настройка конвейера доставки через Docker.
Следование данным рекомендациям позволит поддерживать качественный и легко поддерживаемый проект.
GitHub
gitignore/Go.gitignore at main · github/gitignore
A collection of useful .gitignore templates. Contribute to github/gitignore development by creating an account on GitHub.
#531_GO_TLS
Стандартный путь импорта пакета в Go — определяется правилами именования импортируемых модулей, основанными на концепции уникальности доменного имени разработчика и четкого указания местоположения пакета.
Этот путь должен однозначно определять источник происхождения пакета и обеспечивать возможность его повторного использования другими разработчиками.
Правила формирования стандартного пути импорта в Go:
Доменное имя владельца. Обычно используется полное доменное имя разработчика или организации, которое гарантирует уникальность названия пакета.
Например, если пакет разработан компанией Google, название начинается с "google.com" или аналогичного зарегистрированного домена.
Таким образом, можно видеть, откуда пришел пакет, и избегать конфликтов имен.
Поддомены или префиксы, уточняющие принадлежность проекта.
Например, если разработчик владеет сайтом example.com, путь может выглядеть как example.com/myproject или даже глубже, с указанием подпроекта: example.com/myproject/submodule.
Отсутствие верхнего уровня папок, относящихся к операционной системе или среде разработки.
Традиционно пакеты Go хранятся вне каталога GOPATH, и их расположение фиксируется именно на уровне сайта разработчика.
Пример правильного пути импорта:
Неправильно:
Правильно:
Причина заключается в том, что путь импортирования должен отражать публично доступное сетевое расположение, а не физическое положение файла на компьютере.
Как работает механизм импорта?
Когда вы используете команду go get, система автоматически загружает указанный вами пакет с указанного домена (если этот домен доступен и публикует код, например, на GitHub или другом хостинге).
Использование стандартных путей помогает избежать коллизий и обеспечивает ясность при работе с внешними библиотеками.
Формат полного пути:
Например:
Пакет находится здесь: https://github.com/google/go-prometheus
Импорт осуществляется следующим образом:
Этот путь позволяет другим пользователям точно понимать происхождение пакета и его уникальную идентификацию.
Таким образом, стандартный путь импорта — способ обеспечить однозначную связь между именем пакета и источником его происхождения, позволяя эффективно интегрировать чужие библиотеки и делиться своими собственными решениями.
Стандартный путь импорта пакета в Go — определяется правилами именования импортируемых модулей, основанными на концепции уникальности доменного имени разработчика и четкого указания местоположения пакета.
Этот путь должен однозначно определять источник происхождения пакета и обеспечивать возможность его повторного использования другими разработчиками.
Правила формирования стандартного пути импорта в Go:
Доменное имя владельца. Обычно используется полное доменное имя разработчика или организации, которое гарантирует уникальность названия пакета.
Например, если пакет разработан компанией Google, название начинается с "google.com" или аналогичного зарегистрированного домена.
Таким образом, можно видеть, откуда пришел пакет, и избегать конфликтов имен.
Поддомены или префиксы, уточняющие принадлежность проекта.
Например, если разработчик владеет сайтом example.com, путь может выглядеть как example.com/myproject или даже глубже, с указанием подпроекта: example.com/myproject/submodule.
Отсутствие верхнего уровня папок, относящихся к операционной системе или среде разработки.
Традиционно пакеты Go хранятся вне каталога GOPATH, и их расположение фиксируется именно на уровне сайта разработчика.
Пример правильного пути импорта:
Неправильно:
/home/user/projects/go_project/src/my_package
Правильно:
example.com/go-project/pkg/my_package
Причина заключается в том, что путь импортирования должен отражать публично доступное сетевое расположение, а не физическое положение файла на компьютере.
Как работает механизм импорта?
Когда вы используете команду go get, система автоматически загружает указанный вами пакет с указанного домена (если этот домен доступен и публикует код, например, на GitHub или другом хостинге).
Использование стандартных путей помогает избежать коллизий и обеспечивает ясность при работе с внешними библиотеками.
Формат полного пути:
<DOMAIN>/<PROJECT_NAME>[/SUB_PROJECT][/PACKAGE_NAME]
Например:
Пакет находится здесь: https://github.com/google/go-prometheus
Импорт осуществляется следующим образом:
import "github.com/google/go-prometheus"
Этот путь позволяет другим пользователям точно понимать происхождение пакета и его уникальную идентификацию.
Таким образом, стандартный путь импорта — способ обеспечить однозначную связь между именем пакета и источником его происхождения, позволяя эффективно интегрировать чужие библиотеки и делиться своими собственными решениями.
#532_GO_CMPL_TP
Что представляют собой в Golang интерфейсы "под капотом"?
Интерфейсы в Go — инструмент абстрагирования и композиции типов, позволяющий писать гибкий и расширяемый код.
Понимание реализации интерфейсов "под капотом" помогает лучше осознать, почему интерфейсы работают именно так и как оптимизировать использование ресурсов.
Структура Интерфейса.
Под капотом интерфейс представляет собой двухэлементный кортеж (tuple), состоящий из двух полей:
Поле tab (*itab) — это таблица методов конкретного типа, реализующего интерфейс.
Она хранит следующую информацию:
— тип значения, которое реализует интерфейс;
— методы, доступные для этого типа;
— размер и выравнивание значений типа.
Таким образом, *itab позволяет динамически определить тип объекта во время исполнения программы и вызывает соответствующие методы.
Поле data — это указатель на фактическое значение, соответствующее типу, указанному в таблице методов.
Например, если объект типа MyType, удовлетворяющий интерфейсу, хранится в переменной интерфейса, поле data будет указывать непосредственно на экземпляр структуры MyType.
Как работает реализация?
При присваивании переменной-интерфейсу какого-либо значения, компилятор проверяет, соответствует ли этот тип требуемым методам интерфейса.
Если да, то создается пара (таблица методов, указатель на значение) и сохраняется в структуре интерфейса.
Например:
Здесь при присвоении myVar := MyStruct{}, Go создает структуру интерфейса вида {&itabForMyStruct, &myStructInstance}, где:
itabForMyStruct — таблица методов для типа MyStruct
myStructInstance — сам экземпляр структуры.
Преимущества такого подхода.
Динамическая диспетчеризация — позволяет вызывать разные реализации одного метода для разных объектов одинакового интерфейса.
Инкапсуляция поведения — можно объявлять поведение через интерфейсы, оставляя реализацию конкретных структур скрытыми от внешних модулей.
Производительность — реализация интерфейсов минимально влияет на производительность благодаря статической проверке соответствия интерфейсам на этапе компиляции и использованию таблиц методов для быстрого вызова функций.
Недостатки и ограничения.
Хотя механизм интерфейсов удобен и эффективен, существуют некоторые особенности, которые важно учитывать разработчику:
— Интерфейс занимает минимум два слова памяти даже при пустых структурах, поскольку включает таблицу методов и указатель на значение.
— Присваивание значению интерфейса требует копирование данных, что может влиять на производительность для больших структур.
— Для каждого нового типа, реализующего интерфейс, создается новая таблица методов, что увеличивает расход памяти.
Таким образом, понимание внутренней реализации интерфейсов в Go даёт возможность осознанно проектировать API и улучшать производительность приложений.
Что представляют собой в Golang интерфейсы "под капотом"?
Интерфейсы в Go — инструмент абстрагирования и композиции типов, позволяющий писать гибкий и расширяемый код.
Понимание реализации интерфейсов "под капотом" помогает лучше осознать, почему интерфейсы работают именно так и как оптимизировать использование ресурсов.
Структура Интерфейса.
Под капотом интерфейс представляет собой двухэлементный кортеж (tuple), состоящий из двух полей:
type iface struct {
tab *itab // Таблица методов
data unsafe.Pointer // Указатель на значение
}Поле tab (*itab) — это таблица методов конкретного типа, реализующего интерфейс.
Она хранит следующую информацию:
— тип значения, которое реализует интерфейс;
— методы, доступные для этого типа;
— размер и выравнивание значений типа.
Таким образом, *itab позволяет динамически определить тип объекта во время исполнения программы и вызывает соответствующие методы.
Поле data — это указатель на фактическое значение, соответствующее типу, указанному в таблице методов.
Например, если объект типа MyType, удовлетворяющий интерфейсу, хранится в переменной интерфейса, поле data будет указывать непосредственно на экземпляр структуры MyType.
Как работает реализация?
При присваивании переменной-интерфейсу какого-либо значения, компилятор проверяет, соответствует ли этот тип требуемым методам интерфейса.
Если да, то создается пара (таблица методов, указатель на значение) и сохраняется в структуре интерфейса.
Например:
type MyInterface interface {
Method() string
}
type MyStruct struct{}
func (m MyStruct) Method() string { return "Hello from MyStruct" }
var myVar MyInterface = MyStruct{}Здесь при присвоении myVar := MyStruct{}, Go создает структуру интерфейса вида {&itabForMyStruct, &myStructInstance}, где:
itabForMyStruct — таблица методов для типа MyStruct
myStructInstance — сам экземпляр структуры.
Преимущества такого подхода.
Динамическая диспетчеризация — позволяет вызывать разные реализации одного метода для разных объектов одинакового интерфейса.
func main() {
var mi MyInterface
mi = MyStruct{}
fmt.Println(mi.Method()) // Hello from MyStruct
type AnotherStruct struct {}
func (a AnotherStruct) Method() string { return "Hello from AnotherStruct" }
mi = AnotherStruct{}
fmt.Println(mi.Method()) // Hello from AnotherStruct
}Инкапсуляция поведения — можно объявлять поведение через интерфейсы, оставляя реализацию конкретных структур скрытыми от внешних модулей.
Производительность — реализация интерфейсов минимально влияет на производительность благодаря статической проверке соответствия интерфейсам на этапе компиляции и использованию таблиц методов для быстрого вызова функций.
Недостатки и ограничения.
Хотя механизм интерфейсов удобен и эффективен, существуют некоторые особенности, которые важно учитывать разработчику:
— Интерфейс занимает минимум два слова памяти даже при пустых структурах, поскольку включает таблицу методов и указатель на значение.
— Присваивание значению интерфейса требует копирование данных, что может влиять на производительность для больших структур.
— Для каждого нового типа, реализующего интерфейс, создается новая таблица методов, что увеличивает расход памяти.
Таким образом, понимание внутренней реализации интерфейсов в Go даёт возможность осознанно проектировать API и улучшать производительность приложений.
#533_RUST_TP
Как считать в Rust несколько чисел из стандартного потока которые могут быть введены как на одной строке через пробельные символы, так и на нескольких строках?
Чтобы считать несколько чисел из стандартного потока ввода в Rust, независимо от того, были ли они введены на одной строке через пробелы или распределены по нескольким строкам, можно воспользоваться следующей стратегией:
— Продолжаем чтение потоков ввода до тех пор, пока не встретим конец файла (EOF).
— Каждое введённое значение превращаем в число.
— Все считанные числа добавляем в общий контейнер (например, вектор).
Пример:
Детали реализации:
Буферизованное чтение: используем объект BufReader для эффективного чтения строк из стандартного ввода.
Работа с линиями ввода: итератор .lines() возвращает итерируемые строки, поступающие из потока ввода.
Парсинг каждой строки: каждая строка делится на токены с помощью .split_whitespace(), что позволяет извлекать отдельные числа, даже если они находятся на разных строках.
Фильтрация и обработка ошибок: используя .filter_map(Result::ok), отбрасываем строки, которые невозможно перевести в число, предотвращая аварийное завершение программы.
Собираем числа в единый вектор: итоговые числа собираются в один вектор с помощью метода .collect().
Особенности:
Эта реализация игнорирует строки, содержащие недопустимые символы (не числа). Таким образом, если пользователи вводят буквы или специальные символы среди чисел, они будут пропущены.
Программа прекращает работу, когда достигает конца ввода (обычно при нажатии комбинации клавиш Ctrl+D в терминале Linux/MacOS или Ctrl+Z в Windows).
Тестирование:
Программа вернёт:
Таким образом, можно обрабатывать как однострочные, так и многострочные входные данные.
Как считать в Rust несколько чисел из стандартного потока которые могут быть введены как на одной строке через пробельные символы, так и на нескольких строках?
Чтобы считать несколько чисел из стандартного потока ввода в Rust, независимо от того, были ли они введены на одной строке через пробелы или распределены по нескольким строкам, можно воспользоваться следующей стратегией:
— Продолжаем чтение потоков ввода до тех пор, пока не встретим конец файла (EOF).
— Каждое введённое значение превращаем в число.
— Все считанные числа добавляем в общий контейнер (например, вектор).
Пример:
use std::io::{self, BufRead};
fn main() {
let stdin = io::stdin();
let reader = stdin.lock();
let numbers: Vec<i32> = reader
.lines() // Получаем итератор строк
.flat_map(|line_result| {
let line = line_result.unwrap(); // получаем строку
line.split_whitespace() // разбиваем строку по пробелам
.map(|number_str| number_str.parse::<i32>()) // пытаемся превратить каждую часть в число
.filter_map(Result::ok) // фильтруем только успешно распарсившиеся числа
.collect::<Vec<_>>() // собираем в промежуточный вектор
})
.collect(); // собираем итоговые числа в основной вектор
println!("Прочитанные числа: {:?}", numbers);
}Детали реализации:
Буферизованное чтение: используем объект BufReader для эффективного чтения строк из стандартного ввода.
Работа с линиями ввода: итератор .lines() возвращает итерируемые строки, поступающие из потока ввода.
Парсинг каждой строки: каждая строка делится на токены с помощью .split_whitespace(), что позволяет извлекать отдельные числа, даже если они находятся на разных строках.
Фильтрация и обработка ошибок: используя .filter_map(Result::ok), отбрасываем строки, которые невозможно перевести в число, предотвращая аварийное завершение программы.
Собираем числа в единый вектор: итоговые числа собираются в один вектор с помощью метода .collect().
Особенности:
Эта реализация игнорирует строки, содержащие недопустимые символы (не числа). Таким образом, если пользователи вводят буквы или специальные символы среди чисел, они будут пропущены.
Программа прекращает работу, когда достигает конца ввода (обычно при нажатии комбинации клавиш Ctrl+D в терминале Linux/MacOS или Ctrl+Z в Windows).
Тестирование:
1 2 3
4 5
6
7 8 9
Программа вернёт:
Прочитанные числа: [1, 2, 3, 4, 5, 6, 7, 8, 9]
Таким образом, можно обрабатывать как однострочные, так и многострочные входные данные.
#534_GO_LIB_TP
Какие контейнеры есть в библиотеке container в Golang?
Стандартная библиотека Go включает пакет container, предоставляющий набор различных коллекций и контейнеров для хранения данных.
Основные типы контейнеров, доступные в этом пакете.
Пакет container/list — реализует двунаправленный связанный список:
Type: List
Описание: Поддерживает операции вставки, удаления и перемещения элементов. Элементы хранятся в упорядоченном списке узлов, каждый из которых содержит произвольное значение и ссылки на предыдущие и последующие узлы.
Методы: добавление, удаление, получение первого и последнего элемента, итерация вперед и назад по списку.
Пример использования:
Пакет container/ring — реализует кольцевой буфер (кольцо):
Type: Ring
Описание: Кольцевые списки полезны для реализации циклических алгоритмов обработки данных. Каждый элемент кольца ссылается на следующий элемент и замыкается на первый элемент.
Методы: обход элементов, перемещение указателя, операция вращения кольца.
Пример использования:
Пакет container/heap — предоставляет интерфейс для построения бинарных куч (Binary Heaps):
Interface: Heap
Описание: Куча является структурой данных, позволяющей эффективно находить минимальный/максимальный элемент. Бинарная куча часто используется для приоритетных очередей.
Основные методы интерфейса Heap: Push(), Pop().
Чтобы использовать кучу, необходимо реализовать собственный тип контейнера, удовлетворяющий требованиям интерфейса Heap (реализация методов Len(), Less(), Swap(), Push(), Pop()):
Итоги:
container/list — двунаправленные списки.
container/ring — кольцевые списки.
container/heap — интерфейс для реализации бинарных куч.
Эти контейнеры обеспечивают дополнительные возможности организации данных и облегчают решение многих стандартных задач программирования.
Какие контейнеры есть в библиотеке container в Golang?
Стандартная библиотека Go включает пакет container, предоставляющий набор различных коллекций и контейнеров для хранения данных.
Основные типы контейнеров, доступные в этом пакете.
Пакет container/list — реализует двунаправленный связанный список:
Type: List
Описание: Поддерживает операции вставки, удаления и перемещения элементов. Элементы хранятся в упорядоченном списке узлов, каждый из которых содержит произвольное значение и ссылки на предыдущие и последующие узлы.
Методы: добавление, удаление, получение первого и последнего элемента, итерация вперед и назад по списку.
Пример использования:
l := list.New()
l.PushFront("First")
l.PushBack("Last")
for e := l.Front(); e != nil; e = e.Next() {
fmt.Println(e.Value)
}
Пакет container/ring — реализует кольцевой буфер (кольцо):
Type: Ring
Описание: Кольцевые списки полезны для реализации циклических алгоритмов обработки данных. Каждый элемент кольца ссылается на следующий элемент и замыкается на первый элемент.
Методы: обход элементов, перемещение указателя, операция вращения кольца.
Пример использования:
r := ring.New(3)
r.Value = "One"
r.Next().Value = "Two"
r.Next().Next().Value = "Three"
for p := r.Do(func(x interface{}) { fmt.Println(x) }); p > 0; p-- {}
Пакет container/heap — предоставляет интерфейс для построения бинарных куч (Binary Heaps):
Interface: Heap
Описание: Куча является структурой данных, позволяющей эффективно находить минимальный/максимальный элемент. Бинарная куча часто используется для приоритетных очередей.
Основные методы интерфейса Heap: Push(), Pop().
Чтобы использовать кучу, необходимо реализовать собственный тип контейнера, удовлетворяющий требованиям интерфейса Heap (реализация методов Len(), Less(), Swap(), Push(), Pop()):
type IntHeap []int
func (h IntHeap) Len() int {
return len(h)
}
func (h IntHeap) Less(i, j int) bool {
return h[i] < h[j]
}
func (h IntHeap) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
}
func (h *IntHeap) Push(x interface{}) {
*h = append(*h, x.(int))
}
func (h *IntHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
h := &IntHeap{}
heap.Init(h)
heap.Push(h, 3)
heap.Push(h, 1)
heap.Push(h, 2)
fmt.Println(heap.Pop(h)) // Output: 1
Итоги:
container/list — двунаправленные списки.
container/ring — кольцевые списки.
container/heap — интерфейс для реализации бинарных куч.
Эти контейнеры обеспечивают дополнительные возможности организации данных и облегчают решение многих стандартных задач программирования.
#535_GO_LIB_TP
Какие библиотеки чаще всего используют для взаимодействия с PostgreSQL в проектах на Golang?
Для работы с базой данных PostgreSQL в Go-проектах наиболее часто используются следующие библиотеки:
database/sql + lib/pq — стандартный пакет Go (database/sql) совместно с драйвером lib/pq, который реализует интерфейс подключения к PostgreSQL.
Этот вариант подходит для большинства случаев, поскольку обеспечивает простоту интеграции и совместимость с различными базами данных.
sqlx — расширяет возможности стандартного пакета database/sql.
Она добавляет дополнительные удобные методы для сканирования результатов запросов и управления транзакциями.
Подходит для проектов, которым нужен простой доступ к данным и минимальное количество сторонних зависимостей.
pgx — высокопроизводительная библиотека для работы с PostgreSQL, созданная специально для производительности и функциональности.
Позволяет эффективно управлять соединениями, поддерживает продвинутые SQL-функции и оптимизирует работу с большим объемом данных.
gorm — ORM-библиотека для Go, поддерживающая различные базы данных, включая PostgreSQL.
gorm позволяет легко создавать модели, миграционные скрипты и автоматически синхронизироваться с таблицами. Удобна для больших приложений с частым изменением структуры данных.
Другие менее распространенные варианты:
ent — инструмент автоматического построения моделей и схем данных поверх PostgreSQL. Обеспечивает удобное управление структурами данных и связи между ними.
pop — легковесная ORM для работы с PostgreSQL, ориентированная на удобство и минимализм API.
Каждая из перечисленных библиотек имеет свои преимущества и области применения.
Выбор зависит от требований проекта: производительность, сложность схемы данных, простота реализации CRUD-операций и потребности в ORM-подходе.
Какие библиотеки чаще всего используют для взаимодействия с PostgreSQL в проектах на Golang?
Для работы с базой данных PostgreSQL в Go-проектах наиболее часто используются следующие библиотеки:
database/sql + lib/pq — стандартный пакет Go (database/sql) совместно с драйвером lib/pq, который реализует интерфейс подключения к PostgreSQL.
Этот вариант подходит для большинства случаев, поскольку обеспечивает простоту интеграции и совместимость с различными базами данных.
import (
"database/sql"
_ "github.com/lib/pq"
)
sqlx — расширяет возможности стандартного пакета database/sql.
Она добавляет дополнительные удобные методы для сканирования результатов запросов и управления транзакциями.
Подходит для проектов, которым нужен простой доступ к данным и минимальное количество сторонних зависимостей.
import (
"github.com/jmoiron/sqlx"
)
pgx — высокопроизводительная библиотека для работы с PostgreSQL, созданная специально для производительности и функциональности.
Позволяет эффективно управлять соединениями, поддерживает продвинутые SQL-функции и оптимизирует работу с большим объемом данных.
import (
"github.com/jackc/pgx/v5"
)
gorm — ORM-библиотека для Go, поддерживающая различные базы данных, включая PostgreSQL.
gorm позволяет легко создавать модели, миграционные скрипты и автоматически синхронизироваться с таблицами. Удобна для больших приложений с частым изменением структуры данных.
import (
"gorm.io/gorm"
"gorm.io/driver/postgres"
)
Другие менее распространенные варианты:
ent — инструмент автоматического построения моделей и схем данных поверх PostgreSQL. Обеспечивает удобное управление структурами данных и связи между ними.
pop — легковесная ORM для работы с PostgreSQL, ориентированная на удобство и минимализм API.
Каждая из перечисленных библиотек имеет свои преимущества и области применения.
Выбор зависит от требований проекта: производительность, сложность схемы данных, простота реализации CRUD-операций и потребности в ORM-подходе.
#536_GO_LIB_TP
Пакет context в Golang, для чего он нужен и с чем его едят?
Пакет context в Go — стандартный пакет, используемый для передачи значений между API уровнями и отмены операций во всей цепочке вызовов функций.
Контекст предназначен для упрощения обработки состояний запросов и отмены длительных операций.
Для чего нужен контекст?
Представьте ситуацию: мы пошли за продуктами в магазин и у нас имеется список покупок, но если нам позвонят и скажут, что нужно срочно вернуться домой раньше срока, значит надо бросить покупки и бежать обратно.
Вот такие ситуации решает пакет context в Golang!
Контекст — специальный инструмент, позволяющий передавать инструкции ("указания") от одной части программы к другой.
Представьте контекст как помощника-посредника, который передает важные сигналы другим частям кода.
Какие задачи решаются с помощью пакета context?
Передача сообщений — пусть, у нас запущено много разных процессов одновременно, каждый делает свою работу долго-долго. И вдруг главный процесс понял, что больше не нужны никакие вычисления!
Пакет context помогает передать сообщение "стоп" всем остальным процессам, чтобы они прекратили свою работу.
Пример: пользователь запустил программу, которая скачивает файлы из интернета.
Пользователь нажал кнопку "отмена загрузки" → программа мгновенно прекращает загрузку, даже если там сотни файлов.
Установка ограничений по времени — иногда важно ограничить длительность какого-то процесса.
Представим игру, где игроку дается ровно одна минута на выполнение задания. Через минуту игра автоматически останавливается независимо от того, успел игрок завершить задание или нет.
Пример: программа пытается подключиться к серверу базы данных.
Если соединение не устанавливается за 5 секунд, оно автоматически прерывается.
Передача важной информации — часто бывает, что некоторые процессы требуют какой-то дополнительной информации для своей работы.
Вместо того чтобы передавать кучу аргументов, удобно создать общий контейнер с нужной информацией и передавать его вместе с заданием.
Пример: сервер обрабатывает запрос пользователя и хочет сохранить информацию о пользователе, чтобы каждая последующая обработка знала, кем вызван этот запрос.
Основные интерфейсы и структуры:
Функции из пакета context:
Background() — начинающий контекст, от которого зависят остальные.
Это как основа, от которой начинают строиться новые контексты.
WithCancel() — создает новый контекст, который можно отменить принудительно в любое время.
То есть можно сказать нашему помощнику прекратить всё немедленно, как только потребуется.
WithTimeout() — используется для ограничения продолжительности задачи по времени.
Удобно, когда хочешь убедиться, что задача не продлится дольше определенного периода.
WithDeadline() — похож на предыдущий, но устанавливает точный срок окончания задачи, а не интервал ожидания.
WithValue() — позволяет прикреплять дополнительные данные к контексту.
Очень удобно, если хочешь хранить какую-нибудь важную информацию вроде имени пользователя или сессии, которую потом можно достать из любого места программы.
Тип Context — представляет собой абстрактный тип контекста, поддерживающий два ключевых свойства:
Отменяемость операции.
Передачу произвольных данных («метаданных») внутри приложения.
Контекст создается функциями типа
Каждый новый контекст наследует поведение родительского контекста.
Пример базового использования:
.
Пакет context в Golang, для чего он нужен и с чем его едят?
Пакет context в Go — стандартный пакет, используемый для передачи значений между API уровнями и отмены операций во всей цепочке вызовов функций.
Контекст предназначен для упрощения обработки состояний запросов и отмены длительных операций.
Для чего нужен контекст?
Представьте ситуацию: мы пошли за продуктами в магазин и у нас имеется список покупок, но если нам позвонят и скажут, что нужно срочно вернуться домой раньше срока, значит надо бросить покупки и бежать обратно.
Вот такие ситуации решает пакет context в Golang!
Контекст — специальный инструмент, позволяющий передавать инструкции ("указания") от одной части программы к другой.
Представьте контекст как помощника-посредника, который передает важные сигналы другим частям кода.
Какие задачи решаются с помощью пакета context?
Передача сообщений — пусть, у нас запущено много разных процессов одновременно, каждый делает свою работу долго-долго. И вдруг главный процесс понял, что больше не нужны никакие вычисления!
Пакет context помогает передать сообщение "стоп" всем остальным процессам, чтобы они прекратили свою работу.
Пример: пользователь запустил программу, которая скачивает файлы из интернета.
Пользователь нажал кнопку "отмена загрузки" → программа мгновенно прекращает загрузку, даже если там сотни файлов.
Установка ограничений по времени — иногда важно ограничить длительность какого-то процесса.
Представим игру, где игроку дается ровно одна минута на выполнение задания. Через минуту игра автоматически останавливается независимо от того, успел игрок завершить задание или нет.
Пример: программа пытается подключиться к серверу базы данных.
Если соединение не устанавливается за 5 секунд, оно автоматически прерывается.
Передача важной информации — часто бывает, что некоторые процессы требуют какой-то дополнительной информации для своей работы.
Вместо того чтобы передавать кучу аргументов, удобно создать общий контейнер с нужной информацией и передавать его вместе с заданием.
Пример: сервер обрабатывает запрос пользователя и хочет сохранить информацию о пользователе, чтобы каждая последующая обработка знала, кем вызван этот запрос.
Основные интерфейсы и структуры:
Функции из пакета context:
Background() — начинающий контекст, от которого зависят остальные.
Это как основа, от которой начинают строиться новые контексты.
WithCancel() — создает новый контекст, который можно отменить принудительно в любое время.
То есть можно сказать нашему помощнику прекратить всё немедленно, как только потребуется.
WithTimeout() — используется для ограничения продолжительности задачи по времени.
Удобно, когда хочешь убедиться, что задача не продлится дольше определенного периода.
WithDeadline() — похож на предыдущий, но устанавливает точный срок окончания задачи, а не интервал ожидания.
WithValue() — позволяет прикреплять дополнительные данные к контексту.
Очень удобно, если хочешь хранить какую-нибудь важную информацию вроде имени пользователя или сессии, которую потом можно достать из любого места программы.
Тип Context — представляет собой абстрактный тип контекста, поддерживающий два ключевых свойства:
Отменяемость операции.
Передачу произвольных данных («метаданных») внутри приложения.
Контекст создается функциями типа
WithCancel()
WithTimeout()
WithDeadline().
Каждый новый контекст наследует поведение родительского контекста.
Пример базового использования:
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, time.Second*5)
defer cancel()
// Выполнение длительной операции
result, err := longOperation(ctx)
if ctx.Err() != nil {
fmt.Println("Операция отменена:", ctx.Err())
}
.
Функция Background() — возвращает пустой, незавершенный контекст, подходящий для корневых контекстов запросов.
Его нельзя отменить, и он не имеет дедлайна.
Создает производный контекст с дополнительным значением.
Это полезно для хранения уникальных данных о конкретном запросе, таких как токены авторизации или идентификаторы пользователей.
Функции отмены:
WithCancel(parent Context) — cоздает дочерний контекст, который можно отменить отдельно от родителя. После вызова функции cancel(), связанный контекст получает ошибку отмены.
WithTimeout(parent Context, timeout time.Duration) — устанавливает таймаут для завершения всех действий над данным контекстом.
WithDeadline(parent Context, deadline time.Time) — устанавливает крайнюю точку времени, до которой должна завершаться операция.
Примеры создания временных контекстов:
Метод Err() — возвращает ошибку, соответствующую состоянию контекста.
Возможные состояния:
Нет ошибок (если ничего не произошло),
Ошибка отмены,
Таймаут превышен.
Как правильно использовать?
— Всегда проверяйте состояние контекста перед началом длительной операции.
— Используйте дефер и отмену для своевременного освобождения ресурсов.
— Не передавайте один и тот же контекст нескольким параллельным операциям, создавая отдельный контекст для каждой.
Пакет context нужен, чтобы сообщать программам о важных событиях, ограничивать продолжительность выполнения задач и передавать дополнительную полезную информацию.
Пакет context позволяет эффективно управлять состоянием приложений, обеспечивая отмену асинхронных операций и передачу необходимых метаданных.
Этот механизм значительно облегчает написание отказоустойчивых сервисов и обработку сложных потоков выполнения.
Его нельзя отменить, и он не имеет дедлайна.
ctx := context.Background()
Function WithValue(parent Context, key interface{}, val interface{}) Context:
Создает производный контекст с дополнительным значением.
Это полезно для хранения уникальных данных о конкретном запросе, таких как токены авторизации или идентификаторы пользователей.
type userKey struct{}
ctx = context.WithValue(ctx, userKey{}, "alice")
value := ctx.Value(userKey{})
fmt.Println(value) // Выведет aliceФункции отмены:
WithCancel(parent Context) — cоздает дочерний контекст, который можно отменить отдельно от родителя. После вызова функции cancel(), связанный контекст получает ошибку отмены.
WithTimeout(parent Context, timeout time.Duration) — устанавливает таймаут для завершения всех действий над данным контекстом.
WithDeadline(parent Context, deadline time.Time) — устанавливает крайнюю точку времени, до которой должна завершаться операция.
Примеры создания временных контекстов:
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(time.Hour))
defer cancel()
Метод Err() — возвращает ошибку, соответствующую состоянию контекста.
Возможные состояния:
Нет ошибок (если ничего не произошло),
Ошибка отмены,
Таймаут превышен.
select {
case <-ctx.Done():
fmt.Println("Отмена или таймаут:", ctx.Err())
default:
fmt.Println("Ожидание...")
}Как правильно использовать?
— Всегда проверяйте состояние контекста перед началом длительной операции.
— Используйте дефер и отмену для своевременного освобождения ресурсов.
— Не передавайте один и тот же контекст нескольким параллельным операциям, создавая отдельный контекст для каждой.
Пакет context нужен, чтобы сообщать программам о важных событиях, ограничивать продолжительность выполнения задач и передавать дополнительную полезную информацию.
Пакет context позволяет эффективно управлять состоянием приложений, обеспечивая отмену асинхронных операций и передачу необходимых метаданных.
Этот механизм значительно облегчает написание отказоустойчивых сервисов и обработку сложных потоков выполнения.
#537_RUST_LIB_SDS
В Rust стандартная реализация хэш-таблицы называется HashMap, и она находится в модуле std::collections::HashMap.
HashMap — ассоциативный контейнер, который хранит пары «ключ — значение», где ключи уникальны и хэшируются для быстрого поиска.
Прежде чем использовать HashMap, его нужно импортировать:
Создание пустой HashMap:
Инициализация с данными:
Для использования HashMap::from нужна версия Rust ≥ 1.56 (или включена соответствующая фича в более ранних версиях).
Или через вектор:
Основные методы.
Вставка (insert):
Если ключ уже существует — старое значение заменяется, и возвращается предыдущее значение (опционально):
Получение значения (get):
get возвращает Option<&V>, так как ключ может отсутствовать.
Удаление (remove):
Проверка наличия ключа (contains_key):
Обновление только если ключ отсутствует (entry API):
Или короче:
Если нужно вычислить значение лениво:
Другие полезные методы
len() — возвращает количество пар «ключ-значение»;
is_empty() — проверяет, пуст ли HashMap;
clear() — удаляет все элементы;
iter() — возвращает итератор по парам (&K, &V);
iter_mut() — итератор с возможностью изменения значений;
keys() — итератор по ключам;
values() — итератор по значениям;
values_mut() — итератор по изменяемым значениям;
Пример итерации:
Ограничения на типы.
Ключи в HashMap должны реализовывать трейты:
Hash — для вычисления хэш-кода;
Eq — для сравнения на равенство.
Примеры допустимых типов ключей:
Пример пользовательского ключа:
.
В Rust стандартная реализация хэш-таблицы называется HashMap, и она находится в модуле std::collections::HashMap.
HashMap — ассоциативный контейнер, который хранит пары «ключ — значение», где ключи уникальны и хэшируются для быстрого поиска.
Прежде чем использовать HashMap, его нужно импортировать:
use std::collections::HashMap;
Создание пустой HashMap:
let mut map: HashMap<&str, i32> = HashMap::new();
Инициализация с данными:
use std::collections::HashMap;
let mut map = HashMap::from([
("один", 1),
("два", 2),
("три", 3),
]);
Для использования HashMap::from нужна версия Rust ≥ 1.56 (или включена соответствующая фича в более ранних версиях).
Или через вектор:
let map: HashMap<_, _> = vec![("a", 1), ("b", 2), ("c", 3)].into_iter().collect();
Основные методы.
Вставка (insert):
map.insert("четыре", 4);
Если ключ уже существует — старое значение заменяется, и возвращается предыдущее значение (опционально):
let old_value = map.insert("один", 10); // old_value = Some(1)
Получение значения (get):
match map.get("два") {
Some(&value) => println!("Значение: {}", value),
None => println!("Ключ не найден"),
}get возвращает Option<&V>, так как ключ может отсутствовать.
Удаление (remove):
let removed_value = map.remove("три"); // Option<i32>Проверка наличия ключа (contains_key):
if map.contains_key("один") {
println!("Ключ 'один' существует");
}Обновление только если ключ отсутствует (entry API):
use std::collections::hash_map::Entry;
match map.entry("пять") {
Entry::Occupied(_) => println!("Ключ уже существует"),
Entry::Vacant(e) => {
e.insert(5);
}
}
Или короче:
map.entry("пять").or_insert(5);Если нужно вычислить значение лениво:
map.entry("шесть").or_insert_with(|| 2 * 3);Другие полезные методы
len() — возвращает количество пар «ключ-значение»;
is_empty() — проверяет, пуст ли HashMap;
clear() — удаляет все элементы;
iter() — возвращает итератор по парам (&K, &V);
iter_mut() — итератор с возможностью изменения значений;
keys() — итератор по ключам;
values() — итератор по значениям;
values_mut() — итератор по изменяемым значениям;
Пример итерации:
for (key, value) in &map {
println!("{}: {}", key, value);
}Ограничения на типы.
Ключи в HashMap должны реализовывать трейты:
Hash — для вычисления хэш-кода;
Eq — для сравнения на равенство.
Примеры допустимых типов ключей:
String, &str, i32, (i32, i32), пользовательские структуры (если реализованы Hash + Eq).
Пример пользовательского ключа:
#[derive(Hash, Eq, PartialEq, Debug)]
struct Person {
id: u32,
name: String,
}
let mut people = HashMap::new();
people.insert(Person { id: 1, name: "Алиса".to_string() }, "инженер");
.
Владение и заимствование.
— при вставке String — владение передаётся в HashMap.
— при вставке &str — нужно, чтобы строка жила дольше HashMap.
Пример с владением:
Пример: подсчёт частоты слов:
Вывод:
Производительность.
— Вставка, поиск, удаление — в среднем O(1).
— Худший случай — O(n) при коллизиях, но маловероятен при хорошей хэш-функции.
По умолчанию используется хэш-функция SipHash, безопасная от DoS-атак.
Можно заменить хэшер (например, на ahash или fxhash) для повышения скорости:
Резюме:
HashMap — основной способ хранения ассоциативных данных в Rust.
Требует Hash + Eq от ключей.
Безопасен благодаря системе владения и проверкам на этапе компиляции.
Богатый API: entry, итераторы, вставка/поиск/удаление и т.д.
Гибок: можно кастомизировать хэш-функцию.
Замечания:
Если нужно упорядоченное хранение — смотри в сторону BTreeMap.
Если нужен мульти-ключ или дублирующиеся ключи — hashbrown или другие сторонние решения.
— при вставке String — владение передаётся в HashMap.
— при вставке &str — нужно, чтобы строка жила дольше HashMap.
Пример с владением:
let mut map = HashMap::new();
map.insert(String::from("ключ"), 42); // String передаётся по значению
Пример: подсчёт частоты слов:
use std::collections::HashMap;
let text = "rust is great and rust is fast";
let mut word_count = HashMap::new();
for word in text.split_whitespace() {
*word_count.entry(word.to_lowercase()).or_insert(0) += 1;
}
for (word, count) in &word_count {
println!("{}: {}", word, count);
}
Вывод:
rust: 2
is: 2
great: 1
and: 1
fast: 1
Производительность.
— Вставка, поиск, удаление — в среднем O(1).
— Худший случай — O(n) при коллизиях, но маловероятен при хорошей хэш-функции.
По умолчанию используется хэш-функция SipHash, безопасная от DoS-атак.
Можно заменить хэшер (например, на ahash или fxhash) для повышения скорости:
# Cargo.toml
[dependencies]
ahash = "0.8"
use ahash::AHasher;
use std::hash::BuildHasherDefault;
type FastMap<K, V> = HashMap<K, V, BuildHasherDefault<AHasher>>;
let mut map: FastMap<&str, i32> = HashMap::default();
Резюме:
HashMap — основной способ хранения ассоциативных данных в Rust.
Требует Hash + Eq от ключей.
Безопасен благодаря системе владения и проверкам на этапе компиляции.
Богатый API: entry, итераторы, вставка/поиск/удаление и т.д.
Гибок: можно кастомизировать хэш-функцию.
Замечания:
Если нужно упорядоченное хранение — смотри в сторону BTreeMap.
Если нужен мульти-ключ или дублирующиеся ключи — hashbrown или другие сторонние решения.
🤡1
#538_Cpp_LIB_SDS_STL
Что делает std::cin.tie(NULL); в C++?
Операция std::cin.tie(NULL) — отменяет привязку (tie) потока ввода (std::cin) к потоку вывода (std::cout).
Суть привязки потоков (tie).
Потоки ввода и вывода связаны между собой по умолчанию в C++.
Это означает, что всякий раз, когда производится операция вывода (например, std::cout << something;), система синхронизирует потоки ввода и вывода, гарантируя, что ввод и вывод происходят синхронно.
То есть перед выполнением следующего шага чтения (std::cin) операционная система гарантирует, что предыдущий вывод (std::cout) завершён.
Это полезно в интерактивных приложениях, где нужно обеспечить согласованность ввода и вывода. Однако это замедляет программу, особенно если интенсивное взаимодействие с пользователями не требуется.
Зачем разрывать связь (tie(NULL))?
Повышение производительности — синхронизация между потоками увеличивает накладные расходы.
Если приложение не нуждается в гарантии синхронизации (например, серверные приложения или программы с большими вычислительными нагрузками), разрыв связи между потоками может заметно ускорить работу программы.
Снижение задержки — без привязки вывод на экран не будет ждать завершения операции ввода, что уменьшает задержку, особенно в случаях интенсивного ввода-вывода.
Пример использования:
Допустим, есть следующая программа:
До вызова std::cin.tie(NULL) вывод на экран происходил синхронно с вводом, то есть строка приглашения выводилась только после завершения предыдущего ввода.
После разрыва связи вывод немедленно появляется на экране, не дожидаясь завершения последующих операций ввода.
Важно помнить:
Разрыв связи между потоками может повлиять на визуальное представление приложений, работающих в режиме реального времени.
Будьте осторожны при применении этого трюка в ситуациях, где необходим аккуратный контроль взаимодействия с пользователем.
Что делает std::cin.tie(NULL); в C++?
Операция std::cin.tie(NULL) — отменяет привязку (tie) потока ввода (std::cin) к потоку вывода (std::cout).
Суть привязки потоков (tie).
Потоки ввода и вывода связаны между собой по умолчанию в C++.
Это означает, что всякий раз, когда производится операция вывода (например, std::cout << something;), система синхронизирует потоки ввода и вывода, гарантируя, что ввод и вывод происходят синхронно.
То есть перед выполнением следующего шага чтения (std::cin) операционная система гарантирует, что предыдущий вывод (std::cout) завершён.
Это полезно в интерактивных приложениях, где нужно обеспечить согласованность ввода и вывода. Однако это замедляет программу, особенно если интенсивное взаимодействие с пользователями не требуется.
Зачем разрывать связь (tie(NULL))?
Повышение производительности — синхронизация между потоками увеличивает накладные расходы.
Если приложение не нуждается в гарантии синхронизации (например, серверные приложения или программы с большими вычислительными нагрузками), разрыв связи между потоками может заметно ускорить работу программы.
Снижение задержки — без привязки вывод на экран не будет ждать завершения операции ввода, что уменьшает задержку, особенно в случаях интенсивного ввода-вывода.
Пример использования:
Допустим, есть следующая программа:
#include <iostream>
int main() {
std::cin.tie(NULL); // Отменяем привязку cin к cout
std::cout << "Enter your name: ";
std::string name;
std::cin >> name;
std::cout << "Hello, " << name << "!\n";
return 0;
}
До вызова std::cin.tie(NULL) вывод на экран происходил синхронно с вводом, то есть строка приглашения выводилась только после завершения предыдущего ввода.
После разрыва связи вывод немедленно появляется на экране, не дожидаясь завершения последующих операций ввода.
Важно помнить:
Разрыв связи между потоками может повлиять на визуальное представление приложений, работающих в режиме реального времени.
Будьте осторожны при применении этого трюка в ситуациях, где необходим аккуратный контроль взаимодействия с пользователем.
❤1🤡1
#539_Cpp_PY_LIB_SDS_STL_IdPt
Как в С++, организовать конструкцию подобную switch в Go или match в Rust и Python, которая позволит в качестве меток case использовать не только целочисленные и символьные значения?
Интересный не очень очевидный прием, в нем нет ничего очень умного или хитрого, но таким образом можно заменить конструкцию switch в С++ и при этом мы сможем оперировать не только целочисленными типами, но и строками выражениями и другими объектами (подобная же конструкция с использованием словаря вместо std::map в С++, способна заменить конструкцию math (конструкции switch или подобной ей в Python не было вплоть до версии 3.10)).
Давайте создадим простейший калькулятор:
Таким образом просто добавляя новые пары ключ-значение в map — calculate, можно легко расширить функциональность, добавляя новые операции в наш калькулятор и получать результат вычислений просто обращаясь по ключу.
Все довольно просто, но, по моему мнению, не совсем очевидно, что в качестве значений std::map в С++ или словарей в Python, мы можем использовать выражения.
Как в С++, организовать конструкцию подобную switch в Go или match в Rust и Python, которая позволит в качестве меток case использовать не только целочисленные и символьные значения?
Интересный не очень очевидный прием, в нем нет ничего очень умного или хитрого, но таким образом можно заменить конструкцию switch в С++ и при этом мы сможем оперировать не только целочисленными типами, но и строками выражениями и другими объектами (подобная же конструкция с использованием словаря вместо std::map в С++, способна заменить конструкцию math (конструкции switch или подобной ей в Python не было вплоть до версии 3.10)).
Давайте создадим простейший калькулятор:
#include <iostream>
#include <map>
#include <cmath>
int main() {
auto opd1 = 0., opd2 = 0.;
char operation = '\0';
std::cin >> opd1;
std::cin.ignore();
std::cin >> operation >> opd2;
if ('/' == operation && 0 == opd2) {
std::cerr << "Error. Division by zero.\n";
return 0;
}
std::map<char, int> calculate {
{'+', opd1 + opd2},
{'-', opd1 - opd2},
{'*', opd1 * opd2},
{'/', opd1 / opd2},
{'^', pow(opd1, opd2)},
{'%', static_cast<int>(opd1) % static_cast<int>(opd2)}
};
std::cout << calculate[operation];
return 0;
}
Таким образом просто добавляя новые пары ключ-значение в map — calculate, можно легко расширить функциональность, добавляя новые операции в наш калькулятор и получать результат вычислений просто обращаясь по ключу.
Все довольно просто, но, по моему мнению, не совсем очевидно, что в качестве значений std::map в С++ или словарей в Python, мы можем использовать выражения.
👀2🤡1
#540_RUST_LIB_SDS_IdPt
Как в Rust считать цифры из stdin и записать их в вектор?
В Rust есть несколько способов считать числа из стандартного ввода (`stdin`) и поместить их в вектор (`Vec`).
Считывание одной строки чисел, разделённых пробелами.
Если числа вводятся в одной строке через пробел (например: `1 2 3 4 5`), то:
Замечание: здесь используется `i32`, но можно заменить на `i64`, `f64` и т.д. в зависимости от нужного типа.
Считывание нескольких строк по одному числу в каждой.
Если каждое число вводится на отдельной строке, и известно количество чисел (например, сначала вводится `n`, потом `n` чисел):
Считывание всех чисел до конца ввода (EOF).
Если нужно считывать до конца ввода (например, при перенаправлении из файла или Ctrl+D в терминале):
Этот вариант обрабатывает все строки, разбивая каждую на числа (если в строке несколько чисел через пробел).
Обработка ошибок (более надёжно).
Чтобы избежать паники при ошибке парсинга:
.
Как в Rust считать цифры из stdin и записать их в вектор?
В Rust есть несколько способов считать числа из стандартного ввода (`stdin`) и поместить их в вектор (`Vec`).
Считывание одной строки чисел, разделённых пробелами.
Если числа вводятся в одной строке через пробел (например: `1 2 3 4 5`), то:
use std::io;
fn main() {
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Не удалось прочитать строку");
let numbers: Vec<i32> = input
.trim()
.split_whitespace()
.map(|s| s.parse().expect("Некорректное число"))
.collect();
println!("{:?}", numbers);
}
Замечание: здесь используется `i32`, но можно заменить на `i64`, `f64` и т.д. в зависимости от нужного типа.
Считывание нескольких строк по одному числу в каждой.
Если каждое число вводится на отдельной строке, и известно количество чисел (например, сначала вводится `n`, потом `n` чисел):
use std::io;
fn main() {
let mut n_input = String::new();
io::stdin().read_line(&mut n_input).expect("Не удалось прочитать n");
let n: usize = n_input.trim().parse().expect("Некорректное n");
let mut numbers = Vec::new();
for _ in 0..n {
let mut line = String::new();
io::stdin().read_line(&mut line).expect("Не удалось прочитать число");
let num: i32 = line.trim().parse().expect("Некорректное число");
numbers.push(num);
}
println!("{:?}", numbers);
}
Считывание всех чисел до конца ввода (EOF).
Если нужно считывать до конца ввода (например, при перенаправлении из файла или Ctrl+D в терминале):
use std::io::{self, BufRead};
fn main() {
let stdin = io::stdin();
let numbers: Vec<i32> = stdin
.lock()
.lines()
.map(|line| line.expect("Ошибка чтения строки"))
.flat_map(|line| {
line.split_whitespace()
.map(|s| s.parse::<i32>().expect("Некорректное число"))
})
.collect();
println!("{:?}", numbers);
}Этот вариант обрабатывает все строки, разбивая каждую на числа (если в строке несколько чисел через пробел).
Обработка ошибок (более надёжно).
Чтобы избежать паники при ошибке парсинга:
use std::io;
fn main() {
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Не удалось прочитать строку");
let numbers: Result<Vec<i32>, _> = input
.trim()
.split_whitespace()
.map(|s| s.parse::<i32>())
.collect();
match numbers {
Ok(nums) => println!("{:?}", nums),
Err(e) => eprintln!("Ошибка парсинга: {}", e),
}
}
.
🤡1
#541_RUST_LIB_SDS_IdPt
Как в Rust считать каждую цифру числа, которое считывается из stdin, как элемент вектора?
Если необходимо считать одно большое число как строку, а затем разбить его на отдельные цифры и сохранить каждую как элемент вектора (например, `[1, 2, 1, 3, 4, 3, 4, 5, 6, 7, 5, 3, 4, 8, 9, 0, 0, 3]`), то в Rust это делается очень просто — без парсинга в целое число, потому что число может быть слишком большим для любого примитивного типа (u128 и т.п.).
Решение:
Считываем строку и преобразуем каждый символ в цифру:
Объяснение:
— input.trim() — убирает символы новой строки и пробелы по краям.
— .chars() — перебирает каждый символ в строке.
— .to_digit(10) — пытается преобразовать символ в цифру (возвращает `Option<u32>`).
— expect(...) — завершит программу с ошибкой, если встретится нецифровой символ (например, буква).
— as u8 — приводим к u8, так как цифры от 0 до 9 легко помещаются в один байт.
Если необходимо хранить цифры как i32 или u32 — просто убираем as u8 и оставляем u32:
Безопасный вариант (без паники):
Пример ввода/вывода:
Ввод:
Вывод:
Такой подход не зависит от длины числа, работает даже с числами длиной в тысячи цифр, и очень эффективен.
Как в Rust считать каждую цифру числа, которое считывается из stdin, как элемент вектора?
Если необходимо считать одно большое число как строку, а затем разбить его на отдельные цифры и сохранить каждую как элемент вектора (например, `[1, 2, 1, 3, 4, 3, 4, 5, 6, 7, 5, 3, 4, 8, 9, 0, 0, 3]`), то в Rust это делается очень просто — без парсинга в целое число, потому что число может быть слишком большим для любого примитивного типа (u128 и т.п.).
Решение:
Считываем строку и преобразуем каждый символ в цифру:
use std::io;
fn main() {
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Не удалось прочитать строку");
let digits: Vec<u8> = input
.trim()
.chars()
.map(|c| c.to_digit(10).expect("Некорректный символ (не цифра)") as u8)
.collect();
println!("{:?}", digits);
}
Объяснение:
— input.trim() — убирает символы новой строки и пробелы по краям.
— .chars() — перебирает каждый символ в строке.
— .to_digit(10) — пытается преобразовать символ в цифру (возвращает `Option<u32>`).
— expect(...) — завершит программу с ошибкой, если встретится нецифровой символ (например, буква).
— as u8 — приводим к u8, так как цифры от 0 до 9 легко помещаются в один байт.
Если необходимо хранить цифры как i32 или u32 — просто убираем as u8 и оставляем u32:
let digits: Vec<u32> = input.trim().chars().map(|c| c.to_digit(10).expect("...")).collect();Безопасный вариант (без паники):
use std::io;
fn main() {
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Не удалось прочитать строку");
let digits: Result<Vec<u8>, _> = input
.trim()
.chars()
.map(|c| {
c.to_digit(10)
.map(|d| d as u8)
.ok_or_else(|| format!("Недопустимый символ: '{}'", c))
})
.collect();
match digits {
Ok(v) => println!("{:?}", v),
Err(e) => eprintln!("Ошибка: {}", e),
}
}
Пример ввода/вывода:
Ввод:
121343456753489003
Вывод:
[1, 2, 1, 3, 4, 3, 4, 5, 6, 7, 5, 3, 4, 8, 9, 0, 0, 3]
Такой подход не зависит от длины числа, работает даже с числами длиной в тысячи цифр, и очень эффективен.
🤡1
#542_C_Cpp_IdPt_IF
Я тут увидел в исходнике в одной книжке что функция main() предварительно объявляется, ну т.е.:
Мне и стало интересно, если она объявляется как обычная функция, то можно ли вызвать ее рекурсивно.
Решил провести эксперимент, набросал вот такой код:
И как не удивительно это работает. 😁👍.
В результате работы программы получаем вот такой вывод:
.
Я тут увидел в исходнике в одной книжке что функция main() предварительно объявляется, ну т.е.:
int main(int argc, char** argv):
// и далеее
int main(int argc, char** argv) {
//Тело функции main
return 0;
}
Мне и стало интересно, если она объявляется как обычная функция, то можно ли вызвать ее рекурсивно.
Решил провести эксперимент, набросал вот такой код:
#include <stdio.h>
int main(int argc, char** argv) {
int returnValue;
if (argc == 2) {
sscanf(argv[1], "%d", &returnValue);
}
if (returnValue == 0) {
return returnValue;
}
--returnValue;
argv[1][0] = '0' + returnValue;
printf("argv[1] = %s, returnValue = %d\n", argv[1], returnValue);
return main(argc, argv);
}
И как не удивительно это работает. 😁👍.
В результате работы программы получаем вот такой вывод:
compukter@barracol:~/Programs/C/edu_task$ ./a.out 9
argv[1] = 8, returnValue = 8
argv[1] = 7, returnValue = 7
argv[1] = 6, returnValue = 6
argv[1] = 5, returnValue = 5
argv[1] = 4, returnValue = 4
argv[1] = 3, returnValue = 3
argv[1] = 2, returnValue = 2
argv[1] = 1, returnValue = 1
argv[1] = 0, returnValue = 0
.
🤡1
#543_C_Cpp_ALG_IdPt
Алгоритмы. "Эталонная" реализация Bubble Sort (пузырьковой сортировки) с досрочным завершением, если вектор (массив) уже отсортирован, на С/С++:
.
Алгоритмы. "Эталонная" реализация Bubble Sort (пузырьковой сортировки) с досрочным завершением, если вектор (массив) уже отсортирован, на С/С++:
#include <iostream>
#include <vector>
#include <utility> // std::swap
#include <cstddef> // std::size_t
/**
* @brief Пузырьковая сортировка с ранним выходом.
*
* Алгоритм сортирует контейнер по возрастанию.
* Оптимизирован: если за проход не было обменов — массив уже отсортирован.
*
* @tparam Container тип контейнера (например, std::vector<int>)
* @param arr ссылка на контейнер, который будет отсортирован
*/
template<typename Container>
void bubbleSort(Container& arr) {
const std::size_t n = arr.size();
if (n <= 1) return; // Пустой или одноэлементный контейнер уже отсортирован
bool swapped;
for (std::size_t i = 0; i < n - 1; ++i) {
swapped = false;
// Последние i элементов уже на месте
for (std::size_t j = 0; j < n - 1 - i; ++j) {
if (arr[j] > arr[j + 1]) {
std::swap(arr[j], arr[j + 1]);
swapped = true;
}
}
if (!swapped) break; // Ранний выход — массив отсортирован
}
}
// Специализация для ввода: читаем std::vector<int>
bool readVector(std::vector<int>& numbers) {
std::size_t size = 0;
if (!(std::cin >> size)) {
std::cerr << "Error: invalid or missing input size.\n";
return false;
}
numbers.resize(size);
for (auto& num : numbers) {
if (!(std::cin >> num)) {
std::cerr << "Error: failed to read a number from input.\n";
return false;
}
}
return true;
}
int main() {
std::vector<int> numbers;
if (!readVector(numbers)) {
return EXIT_FAILURE;
}
bubbleSort(numbers);
// Вывод без завершающего пробела
for (std::size_t i = 0; i < numbers.size(); ++i) {
if (i != 0) {
std::cout << ' ';
}
std::cout << numbers[i];
}
std::cout << '\n';
return EXIT_SUCCESS;
}
.
🔥1🤡1
#544_C_IdPt_TP
C — idioms!
Термин "C idioms" (идиомы C) означает устоявшиеся, идиоматические (привычные для опытных программистов на C) паттерны написания кода, которые обеспечивают:
— Безопасность (предотвращение утечек, разыменования NULL, переполнений),
— Портабельность,
— Читаемость для других C-разработчиков,
— Эффективность,
— Согласованность с философией языка C.
Основные идиомы C, сгруппированные по категориям.
Работа с памятью.
1. malloc через sizeof *ptr
Почему:
не зависит от типа T, не нужно дублировать имя типа. Безопасно при рефакторинге.
Пример:
Плохо:
malloc(sizeof(Node) * n) — дублирование имени, риск ошибки.
2. Всегда проверяй результат malloc
В embedded или специализированных системах иногда пропускают, но в общем случае — обязательно.
3. Инициализация после malloc — используй calloc или memset если нужна обнулённая память:
Или:
4. Освобождение и обнуление указателя (defensive programming)
Особенно полезно в крупных функциях или при повторном использовании переменной.
Работа с указателями и структурами.
1. Используй typedef для структур (но осторожно!)
Позволяет писать Node *, а не struct node * — чище.
Но некоторые purists (например, Linus Torvalds) против, т.к. скрывает тот факт, что это структура.
Однако в большинстве проектов — принято.
2. Передача структур: по указателю, если модифицируешь
Передача по значению (List l) копирует всю структуру — дорого, если она большая.
3. Используй const агрессивно
Помогает компилятору, предотвращает ошибки, улучшает интерфейс.
3. Циклы и итерации
1. Идиома обхода связного списка
Коротко, читаемо, идиоматично.
2. Освобождение списка
Не теряешь указатель на следующий узел до освобождения текущего.
Работа с файлами и вводом-выводом.
1. Проверка scanf, fopen, fread
Никогда не предполагай, что ввод/файл успешен.
2. Чтение строк — fgets, а не gets
gets — запрещён (уязвимость к переполнению буфера).
.
C — idioms!
Термин "C idioms" (идиомы C) означает устоявшиеся, идиоматические (привычные для опытных программистов на C) паттерны написания кода, которые обеспечивают:
— Безопасность (предотвращение утечек, разыменования NULL, переполнений),
— Портабельность,
— Читаемость для других C-разработчиков,
— Эффективность,
— Согласованность с философией языка C.
Основные идиомы C, сгруппированные по категориям.
Работа с памятью.
1. malloc через sizeof *ptr
T *p = malloc(sizeof *p * n);
Почему:
не зависит от типа T, не нужно дублировать имя типа. Безопасно при рефакторинге.
Пример:
int *arr = malloc(sizeof *arr * 10); // выделяет 10 int'ов
Node *node = malloc(sizeof *node); // выделяет 1 узел
Плохо:
malloc(sizeof(Node) * n) — дублирование имени, риск ошибки.
2. Всегда проверяй результат malloc
void *p = malloc(n);
if (!p) {
// обработка ошибки: exit, return error code, etc.
}
В embedded или специализированных системах иногда пропускают, но в общем случае — обязательно.
3. Инициализация после malloc — используй calloc или memset если нужна обнулённая память:
int *arr = calloc(n, sizeof *arr); // автоматически обнуляет
Или:
int *arr = malloc(sizeof *arr * n);
if (arr) memset(arr, 0, sizeof *arr * n);
4. Освобождение и обнуление указателя (defensive programming)
free(ptr);
ptr = NULL; // предотвращает использование "висячего" указателя
Особенно полезно в крупных функциях или при повторном использовании переменной.
Работа с указателями и структурами.
1. Используй typedef для структур (но осторожно!)
typedef struct node {
int data;
struct node *next;
} Node;Позволяет писать Node *, а не struct node * — чище.
Но некоторые purists (например, Linus Torvalds) против, т.к. скрывает тот факт, что это структура.
Однако в большинстве проектов — принято.
2. Передача структур: по указателю, если модифицируешь
void modify_list(List *l); // изменяет список
void print_list(const List *l); // не изменяет
Передача по значению (List l) копирует всю структуру — дорого, если она большая.
3. Используй const агрессивно
int get_size(const List *l); // обещаешь не менять
Помогает компилятору, предотвращает ошибки, улучшает интерфейс.
3. Циклы и итерации
1. Идиома обхода связного списка
for (Node *p = head; p != NULL; p = p->next) {
// обработка
}Коротко, читаемо, идиоматично.
2. Освобождение списка
while (head) {
Node *next = head->next;
free(head);
head = next;
}Не теряешь указатель на следующий узел до освобождения текущего.
Работа с файлами и вводом-выводом.
1. Проверка scanf, fopen, fread
if (scanf("%d", &x) != 1) { /* ошибка */ }
if ((fp = fopen("file", "r")) == NULL) { /* ошибка */ }Никогда не предполагай, что ввод/файл успешен.
2. Чтение строк — fgets, а не gets
char buf[256];
if (fgets(buf, sizeof buf, stdin)) {
// обрезать \n при необходимости
}
gets — запрещён (уязвимость к переполнению буфера).
.
🤡1
Функции и интерфейсы
1. Возвращай коды ошибок (или используй exit)
Альтернатива — аварийное завершение (exit) при критических ошибках (например, malloc failed).
2. Инициализация через составной литерал или функцию
{0} — стандартный способ обнулить структуру.
Макросы и препроцессор (осторожно!).
1. Защита от повторного включения
Стандарт для заголовочных файлов.
2. Макросы для мин/макс (осторожно: avoid side effects!)
Но лучше использовать функции, если типы известны.
Безопасность и надёжность.
1. Избегай "магических чисел":
Улучшает читаемость и поддержку.
2. Используй size_t для размеров и индексов:
size_t — беззнаковый тип, возвращаемый sizeof, strlen, malloc и т.д.
Но будь осторожен при сравнении с int — возможны неожиданные преобразования.
3. Проверяй границы массивов (если не используешь безопасные абстракции)
C не проверяет границы — это твоя забота.
Современные (C99/C11) идиомы.
1. Объявление переменных в заголовке цикла
Чисто, локально, безопасно.
2. Designated initializers (C99)
Читаемо и явно.
3. Compound literals (C99)
Полезно для временных объектов.
Ошибки, которых стоит избегать (анти-идиомы):
void main() — не стандартно. Используй
или
Приведение malloc:
В C не нужно, может скрыть ошибку, если забыть
#include <stdlib.h>
Игнорирование возвращаемого значения scanf, fopen — ведёт к UB или зацикливанию.
fflush(stdin) — неопределённое поведение (только для outputStreams!).
Возврат указателя на локальную переменную — указывает в "никуда" после выхода из функции.
C idioms — это не просто "как писать", а "как писать правильно в духе C":
— минимализм,
— явность,
— контроль над ресурсами,
— доверяй, но проверяй,
— компилятор — друг, но не замена внимательности.
Изучение и применение этих идиом делает код:
— надёжным,
— понятным другим C-программистам,
— легко поддерживаемым.
1. Возвращай коды ошибок (или используй exit)
int create_list(List *l); // возвращает 0 при успехе, -1 при ошибке
Альтернатива — аварийное завершение (exit) при критических ошибках (например, malloc failed).
2. Инициализация через составной литерал или функцию
List l = {0}; // все поля = 0 / NULL
// или
List l = make_list();{0} — стандартный способ обнулить структуру.
Макросы и препроцессор (осторожно!).
1. Защита от повторного включения
#ifndef MY_HEADER_H
#define MY_HEADER_H
// содержимое
#endif
Стандарт для заголовочных файлов.
2. Макросы для мин/макс (осторожно: avoid side effects!)
#define MIN(a, b) ((a) < (b) ? (a) : (b))
Но лучше использовать функции, если типы известны.
Безопасность и надёжность.
1. Избегай "магических чисел":
#define MAX_NODES 1000
Улучшает читаемость и поддержку.
2. Используй size_t для размеров и индексов:
for (size_t i = 0; i < n; i++) // n — размер массива
size_t — беззнаковый тип, возвращаемый sizeof, strlen, malloc и т.д.
Но будь осторожен при сравнении с int — возможны неожиданные преобразования.
3. Проверяй границы массивов (если не используешь безопасные абстракции)
C не проверяет границы — это твоя забота.
Современные (C99/C11) идиомы.
1. Объявление переменных в заголовке цикла
for (int i = 0; i < n; i++) // i виден только в цикле
Чисто, локально, безопасно.
2. Designated initializers (C99)
struct Point p = {.x = 10, .y = 20};
List l = {.first = NULL, .last = NULL, .size = 0};Читаемо и явно.
3. Compound literals (C99)
draw_point((struct Point){.x = 5, .y = 5});Полезно для временных объектов.
Ошибки, которых стоит избегать (анти-идиомы):
void main() — не стандартно. Используй
int main(void) {}или
int main(int argc, char *argv[])
Приведение malloc:
int* pi = (int*)malloc(size * sizeof(int));
В C не нужно, может скрыть ошибку, если забыть
#include <stdlib.h>
Игнорирование возвращаемого значения scanf, fopen — ведёт к UB или зацикливанию.
fflush(stdin) — неопределённое поведение (только для outputStreams!).
Возврат указателя на локальную переменную — указывает в "никуда" после выхода из функции.
C idioms — это не просто "как писать", а "как писать правильно в духе C":
— минимализм,
— явность,
— контроль над ресурсами,
— доверяй, но проверяй,
— компилятор — друг, но не замена внимательности.
Изучение и применение этих идиом делает код:
— надёжным,
— понятным другим C-программистам,
— легко поддерживаемым.
👍3😁1🤡1
#545_GO_IdPt_TP
Как в Go идиоматически верно считывать данные из стандартного вввода.
В Go идиоматически считывать данные из стандартного ввода os.Stdin можно несколькими способами, в зависимости от того, что именно необходимо считать:
1. Считать всю строку (включая пробелы)
Используйте bufio.Scanner — это самый распространённый и идиоматичный способ:
✅ Подходит для построчного ввода.
⚠️ Не подходит для чтения бинарных данных или очень длинных строк (ограничение по умолчанию ~64 КБ).
2. Считать отдельные слова или токены
Если нужно читать по словам (разделённым пробелами), можно использовать fmt.Scan или fmt.Scanf:
Для нескольких значений:
✅ Просто и удобно для парсинга структурированного ввода.
❌ Не подходит для строк с пробелами (только до первого пробела).
3. Считать всё содержимое stdin сразу
Если нужно прочитать весь ввод целиком (например, из pipe):
⚠️ Осторожно: может быть опасно при большом объёме данных (например, если stdin — бесконечный поток).
4. Чтение побайтово или поблочно
Если нужен низкоуровневый контроль:
Используется редко, только при необходимости.
Рекомендации:
— Для интерактивного ввода строк → bufio.Scanner.
— Для парсинга чисел/слов → fmt.Scan.
— Для обработки всего stdin (например, как фильтр) → io.ReadAll.
Как в Go идиоматически верно считывать данные из стандартного вввода.
В Go идиоматически считывать данные из стандартного ввода os.Stdin можно несколькими способами, в зависимости от того, что именно необходимо считать:
1. Считать всю строку (включая пробелы)
Используйте bufio.Scanner — это самый распространённый и идиоматичный способ:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
fmt.Print("Введите строку: ")
if scanner.Scan() {
line := scanner.Text()
fmt.Println("Вы ввели:", line)
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "Ошибка при чтении:", err)
}
}
✅ Подходит для построчного ввода.
⚠️ Не подходит для чтения бинарных данных или очень длинных строк (ограничение по умолчанию ~64 КБ).
2. Считать отдельные слова или токены
Если нужно читать по словам (разделённым пробелами), можно использовать fmt.Scan или fmt.Scanf:
var word string
fmt.Print("Введите слово: ")
_, err := fmt.Scan(&word)
if err != nil {
fmt.Fprintln(os.Stderr, "Ошибка:", err)
return
}
fmt.Println("Вы ввели:", word)
Для нескольких значений:
var a, b int
fmt.Scan(&a, &b) // читает два целых числа
✅ Просто и удобно для парсинга структурированного ввода.
❌ Не подходит для строк с пробелами (только до первого пробела).
3. Считать всё содержимое stdin сразу
Если нужно прочитать весь ввод целиком (например, из pipe):
data, err := io.ReadAll(os.Stdin)
if err != nil {
fmt.Fprintln(os.Stderr, "Ошибка:", err)
return
}
fmt.Printf("Получено %d байт: %s\n", len(data), string(data))
⚠️ Осторожно: может быть опасно при большом объёме данных (например, если stdin — бесконечный поток).
4. Чтение побайтово или поблочно
Если нужен низкоуровневый контроль:
buffer := make([]byte, 1024)
n, err := os.Stdin.Read(buffer)
if err != nil && err != io.EOF {
fmt.Fprintln(os.Stderr, "Ошибка:", err)
}
fmt.Printf("Прочитано %d байт: %s\n", n, string(buffer[:n]))
Используется редко, только при необходимости.
Рекомендации:
— Для интерактивного ввода строк → bufio.Scanner.
— Для парсинга чисел/слов → fmt.Scan.
— Для обработки всего stdin (например, как фильтр) → io.ReadAll.
👍2🤡1
#546_Cpp_GO_RUST_IdPt_TP
Что общего и в чем различия между методами класса или структуры в С++ и трейтами в Rust или интерфейсами в Go?
Давайте рассмотрим аналогию с монолитной и микросервисной архитектурой.
Действительно, можно представить различия между классами и методами в C++ и трейтами в Rust следующим образом:
Класс и методы в C++:
Представьте большой монолитный сервер, который несет в себе и интерфейс (контракт), и всю необходимую реализацию сразу внутри себя. Этот сервер не только описывает API, но и сам предоставляет готовую бизнес-логику и функции. В случае с классами в C++ это моноблок, в котором совмещены:
— Контракты (интерфейсы, методы, свойства).
— Реализации этих контрактов (код методов).
Класс как единый монолитный объект, в котором собрано всё сразу: спецификация поведения и готовые реализации методов.
Трейты в Rust или интерфейсы в Go:
Представь систему микросервисов, где сервис-интерфейс выступает как декларация соглашения (API), а сервисы-реализации — это отдельные микросервисы, которые предоставляют функциональные блоки, совместимые с этим соглашением.
Трейты и интерфейсы:
— Только объявляют контракт (описывают методы и поведение).
— Сами не содержат никакой реализации.
— Реализация методов предоставляется отдельными типами, которые хотят поддержать этот контракт.
Таким образом, трейты и интерфейсы становятся легкими и гибкими частями инфраструктуры, свободными от жесткой связи с конкретной реализацией. Подобно микросервисам, трейты дают свободу сочетать различные реализации без необходимости тесной интеграции с остальным кодом.
Монолитный подход (класс и методы в C++) противопоставляется микромодульному подходу (трейты в Rust, интерфейсы в Go).
Монолитные классы собирают в себе контракт и реализацию, тогда как трейты и интерфейсы предоставляют чистый контракт, оставляя реализацию типам данных, реализующим этот контракт.
Что общего и в чем различия между методами класса или структуры в С++ и трейтами в Rust или интерфейсами в Go?
Давайте рассмотрим аналогию с монолитной и микросервисной архитектурой.
Действительно, можно представить различия между классами и методами в C++ и трейтами в Rust следующим образом:
Класс и методы в C++:
Представьте большой монолитный сервер, который несет в себе и интерфейс (контракт), и всю необходимую реализацию сразу внутри себя. Этот сервер не только описывает API, но и сам предоставляет готовую бизнес-логику и функции. В случае с классами в C++ это моноблок, в котором совмещены:
— Контракты (интерфейсы, методы, свойства).
— Реализации этих контрактов (код методов).
Класс как единый монолитный объект, в котором собрано всё сразу: спецификация поведения и готовые реализации методов.
Трейты в Rust или интерфейсы в Go:
Представь систему микросервисов, где сервис-интерфейс выступает как декларация соглашения (API), а сервисы-реализации — это отдельные микросервисы, которые предоставляют функциональные блоки, совместимые с этим соглашением.
Трейты и интерфейсы:
— Только объявляют контракт (описывают методы и поведение).
— Сами не содержат никакой реализации.
— Реализация методов предоставляется отдельными типами, которые хотят поддержать этот контракт.
Таким образом, трейты и интерфейсы становятся легкими и гибкими частями инфраструктуры, свободными от жесткой связи с конкретной реализацией. Подобно микросервисам, трейты дают свободу сочетать различные реализации без необходимости тесной интеграции с остальным кодом.
Монолитный подход (класс и методы в C++) противопоставляется микромодульному подходу (трейты в Rust, интерфейсы в Go).
Монолитные классы собирают в себе контракт и реализацию, тогда как трейты и интерфейсы предоставляют чистый контракт, оставляя реализацию типам данных, реализующим этот контракт.
👍2🔥1👏1🤡1
#547_IF
Новый авторский сленговый термин интересно приживется или нет.
"ПОДОКОННИК" — человек пользующийся Windows (работающий "под "Окнами") ✌️😂.
Новый авторский сленговый термин интересно приживется или нет.
"ПОДОКОННИК" — человек пользующийся Windows (работающий "под "Окнами") ✌️😂.