Язык программирования Jakt
Jakt — это безопасный для памяти язык системного программирования.
В настоящее время он транслируется в C++.
Примечание. Язык активно разрабатывается.
Примечание: Если вы клонируете на ПК с Windows (не WSL), убедитесь, что ваш Git-клиент сохраняет окончания строк как \n
. Вы можете установить это как глобальную конфигурацию через git config --global core.autocrlf false
.
Для трансляции в C++ требуется clang
. Убедитесь, что он установлен.
jakt file.jakt
./build/file
См. здесь.
Следующие стратегии используются для обеспечения безопасности памяти:
В Jakt есть три типа указателей:
unsafe
.)Нулевые указатели невозможны в безопасном режиме, но указатели можно обернуть в Optional
, то есть Optional<T>
или T?
для краткости.
int
. Все преобразования должны быть явными.Для случаев, когда желательно бесшумное целочисленное переполнение, существуют явные функции, которые предоставляют эту функциональность.
На чтение кода тратится гораздо больше времени, чем на его написание. По этой причине Jakt уделяет большое внимание читаемости.
Некоторые из функций, способствующих более читаемым программам:
object.function(width: 10, height: 5)
).enum
(можно сказать Foo
вместо MyEnum::Foo
).match
.foo?.bar?.baz
(с возможностью сбоя) и foo!.bar!.baz
(безошибочная)).None
для дополнительных параметров (foo ?? bar
даёт foo
, если у foo
есть значение, иначе bar
).defer
..
(никогда ->
).ErrorOr<T>
и выделенными ключевыми словами try
/ must
.Jakt гибок в том, как проект может быть структурирован с помощью встроенной системы модулей.
import a // (1)
import a { use_cool_things } // (2)
import fn() // (3)
import relative foo::bar // (4)
import relative parent::foo::baz // (5)
import relative parent(3)::foo::baz // (6)
use_cool_things()
из модуля a
.relative
, когда модуль является подпутью каталога, содержащего файл.У Jakt есть стандартная библиотека, доступ к которой осуществляется через пространство имён jakt::
:
import jakt::arguments
import jakt::libc::io { system }
Стандартная библиотека Jakt находится в зачаточном состоянии, поэтому рассмотрите возможность внести свой вклад!
При вызове функции необходимо указать имя каждого аргумента при передаче:
rect.set_size(width: 640, height: 480)
Есть два исключения из этого правила:
anon
, допускается пропуск метки аргумента.Существует два основных способа объявления структуры в Jakt: struct
и class
.
struct
Основной синтаксис:
struct Point {
x: i64
}
``` **Структуры в Jakt имеют семантику значений:**
* Переменные, содержащие структуру, всегда имеют уникальный экземпляр структуры.
* Копирование экземпляра структуры всегда создаёт глубокую копию.
**Jakt генерирует конструктор по умолчанию для структур.** Он принимает все поля по имени. Для структуры Point это выглядит так:
```jakt
Point(x: i64, y: i64)
Члены структуры по умолчанию являются публичными.
Тот же базовый синтаксис, что и у struct:
class Size {
width: i64
height: i64
public fn area(this) => .width * .height
}
Классы в Jakt имеют ссылочную семантику:
Члены класса по умолчанию являются частными.
И структуры, и классы могут иметь функции-члены.
Существует три вида функций-членов:
class Foo {
fn func() => println("Hello!")
}
// Foo::func() можно вызвать без объекта.
Foo::func()
class Foo {
fn func(this) => println("Hello!")
}
// Foo::func() может быть вызван только для экземпляра Foo.
let x = Foo()
x.func()
class Foo {
x: i64
fn set(mut this, anon x: i64) {
this.x = x
}
}
// Foo::set() может вызываться только для изменяемого Foo:
mut foo = Foo(x: 3)
foo.set(9)
Чтобы уменьшить повторяющийся спам this в методах, сокращение .foo расширяется до this.foo.
Строки предоставляются в языке в основном как тип String, который является подсчитываемым по ссылкам (и размещённым в куче) строковым типом. Литералы строк записываются двойными кавычками, например, «Hello, world!».
По умолчанию строковые литералы имеют тип String; однако они могут использоваться для неявного создания любого типа, реализующего признак FromStringLiteral (или ThrowingFromStringLiteral). В настоящее время в прелюдии языка только StringView реализует этот признак, который можно использовать только для ссылки на строки со статическим временем жизни:
let foo: StringView = "foo" // Эта строка не размещается в куче, а foo — всего лишь толстый указатель на статическую строку.
Перегруженные строковые литералы можно использовать, предоставив подсказку типа, будь то явные аннотации типов или передача литерала функции, ожидающей определённый тип:
struct NotString implements(FromStringLiteral) {
fn from_string_literal(anon string: StringView) -> NotString => NotString()
}
fn test(x: NotString) {}
fn main() {
let foo: NotString = "foo"
test(x: "Some string literal")
}
Динамические массивы предоставляются через встроенный тип Array. Они могут расти и уменьшаться во время выполнения.
Array безопасен для памяти:
// Функция, которая принимает Array<i64> и возвращает Array<String>
fn foo(numbers: [i64]) -> [String] {
...
}
// Array<i64> с 256 элементами, все инициализированы нулём.
let values = [0; 256]
// Array<String> с 3 элементами: «foo», «bar» и «baz».
let values = ["foo", "bar", "baz"]
fn main() {
let dict = ["a": 1, "b": 2]
println("{}", dict["a"])
}
// Функция, которая принимает Dictionary<i64, String> и возвращает Dictionary<String, bool>
fn foo(numbers: [i64: String]) -> [String: bool] {
...
}
// Словарь <String, i64> с 3 элементами.
let values = ["foo": 500, "bar": 600, "baz": 700]
fn main() {
let set = {1, 2, 3}
println("{}", set.contains(1))
println("{}", set.contains(5))
}
fn main() {
let x = ("a", 2, true)
println("{}", x.1)
}
match
;match
;match
.enum MyOptional<T> {
Some(T)
None
}
fn value_or_default<T>(anon x: MyOptional<T>, default: T) -> T {
return match x {
Some(value) => {
let stuff = maybe_do_stuff_with(value)
let more_stuff = stuff.do_some_more_processing()
yield more_stuff
}
None => default
}
}
enum Foo {
StructLikeThingy (
field_a: i32
field_b: i32
)
}
fn look_at_foo(anon x: Foo) -> i32 {
match x {
StructLikeThingy(field_a: a, field_b) => {
return a + field_b
}
}
}
enum AlertDescription: i8 {
CloseNotify = 0
UnexpectedMessage = 10
BadRecordMAC = 20
// etc
}
// Использование в match:
fn do_nothing_in_particular() => match AlertDescription::CloseNotify {
CloseNotify => { ... }
UnexpectedMessage => { ... }
BadRecordMAC => { ... }
}
Дакт поддерживает универсальные структуры и универсальные функции.
fn id<T>(anon x: T) -> T {
return x
}
fn main() {
let y = id(3)
println("{}", y + 1000)
}
struct Foo<T> {
x: T
}
fn main() {
let f = Foo(x: 100)
println("{}", f.x)
}
struct MyArray<T, comptime U> {
// NOTE: В настоящее время нет способа получить доступ к значению 'U', обращение к 'U' допустимо только как к типу.
data: [T]
}
namespace Greeters {
fn greet() {
println("Well, hello friends")
}
}
fn main() {
Greeters::greet()
}
В Дакте есть два встроенных оператора приведения типов.
as? T
: Возвращает Optional<T>
, пустое, если исходное значение не может быть преобразовано в T
.as! T
: Возвращает значение T
, прерывает работу программы, если исходное значение не может быть преобразовано в T
.Оператор as
может выполнять следующие действия (обратите внимание, что реализация может не совпадать):
true
для любого значения, кроме 0, которое равно false
.false → 0
и true → 1
, даже для чисел с плавающей запятой.T
будет увеличивать значение на... Сильная и слабая ссылки. Приведение типов
reference count as expected; that's the preferred way of creating a strong reference from a weak reference. A cast from and to raw T
is unsafe.
В стандартной библиотеке доступны дополнительные приведения. Два важных из них — as_saturated
и as_truncated
, которые приводят целочисленные значения с насыщением до границ или усечением битов соответственно.
Чтобы сделать дженерики более мощными и выразительными, вы можете добавить дополнительную информацию к ним:
trait Hashable<Output> {
fn hash(self) -> Output
}
class Foo implements(Hashable<i64>) {
fn hash(self) => 42
}
Traits могут использоваться для добавления ограничений к универсальным типам, а также предоставляют реализации по умолчанию на основе минимального набора требований — например:
trait Fancy {
fn do_something(this) -> void
fn do_something_twice(this) -> void {
.do_something()
.do_something()
}
}
struct Boring implements(Fancy) {
fn do_something(this) -> void {
println("I'm so boring")
}
// Note that we don't have to implement `do_something_twice` here, because it has a default implementation.
}
struct Better implements(Fancy) {
fn do_something(this) -> void {
println("I'm not boring")
}
// However, a custom implementation is still valid.
fn do_something_twice(this) -> void {
println("I'm not boring, but I'm doing it twice")
}
}
У traits могут быть методы, которые ссылаются на другие traits как типы, что можно использовать для описания иерархии traits:
trait ConstIterable<T> {
fn next(this) -> T?
}
trait IntoIterator<T> {
// Note how the return type is a reference to the ConstIterable trait (and not a concrete type)
fn iterator(this) -> ConstIterable<T>
}
Операторы реализованы как traits и могут быть перегружены путём их реализации для данного типа:
struct Foo implements(Add<Foo, Foo>) {
x: i32
fn add(this, anon rhs: Foo) -> Foo {
return Foo(x: .x + other.x)
}
}
Связь между операторами и traits следующая (обратите внимание, что @
используется в качестве заполнителя для имени или символа любого бинарного оператора):
Оператор | Trait | Метод Name | Derived From Method |
---|---|---|---|
+ |
Add |
add |
- |
- |
Subtract |
subtract |
- |
* |
Multiply |
multiply |
- |
/ |
Divide |
divide |
- |
% |
Modulo |
modulo |
- |
< |
Compare |
less_than |
compare |
> |
Compare |
greater_than |
compare |
<= |
Compare |
less_than_or_equal |
compare |
>= |
Compare |
greater_than_or_equal |
compare |
== |
Equal |
equals |
- |
!= |
Equal |
not_equals |
equals |
@= |
@Assignment |
@_assign |
- |
Другие операторы ещё не были преобразованы в traits, не решены или реализованы:
Operator | Описание | Статус |
---|---|---|
& |
Побитовое И | Не решено |
| |
Побитовое Или | Не решено |
^ |
Побитовое Xor | Не решено |
~ |
Побитовое Не | Не решено |
<< |
Побитовый Сдвиг влево | Не решено |
>> |
Побитовый сдвиг вправо | Не решено |
and |
Логическое И | Не решено |
or |
Логическое Или | Не решено |
not |
Логическое Не | Не решено |
= |
Присвоение | Не решено |
(Ещё не реализован)
Для обеспечения безопасности необходимо провести несколько видов анализа (не исчерпывающий список):
Функции, которые могут завершиться с ошибкой вместо нормального возврата, помечаются ключевым словом throws
:
fn task_that_might_fail() throws -> usize {
if problem {
throw Error::from_errno(EPROBLEM)
}
...
return result
}
fn task_cannot_fail() -> usize {
...
return result
}
В отличие от таких языков, как C++ и Java, ошибки не разворачивают стек вызовов автоматически. Вместо этого они всплывают к ближайшему вызывающему объекту.
Если ничего не указано, вызов функции, которая throws
, из функции, которая также throws
, будет неявно распространять ошибки.
Синтаксис для перехвата ошибок
Чтобы перехватить ошибки локально, а не позволить им всплывать к вызывающей стороне, используйте конструкцию try
/catch
, например:
try {
task_that_might_fail()
} catch error {
println("Caught error: {}", error)
}
Существует также более короткая форма:
try task_that_might_fail() catch error {
println("Caught error: {}", error)
}
Перебрасывание ошибок
(Не реализовано)
Встроенный C++
Для лучшей совместимости с существующим кодом на C++, а также в ситуациях, когда возможностей Jakt в блоках unsafe
недостаточно, существует возможность встраивания встроенного кода на C++ в программу в виде блоков cpp
:
mut x = 0
unsafe {
cpp {
"x = (i64)&x;"
}
}
println("{}", x)
Ссылки
Значения и объекты можно передавать по ссылке в некоторых ситуациях, где это доказуемо безопасно.
Ссылка может быть либо неизменной (по умолчанию), либо изменяемой.
Синтаксис ссылочного типа
&T
— неизменяемая ссылка на значение типа T
.&mut T
— изменяемая ссылка на значение типа T
.Синтаксис выражения ссылки
&foo
создаёт неизменяемую ссылку на переменную foo
.&mut foo
создаёт изменяемую ссылку на переменную foo
.Разыменование ссылки
Чтобы «получить значение» из ссылки, её нужно разыменовать с помощью оператора *
, однако компилятор автоматически разыменует ссылки, если разыменование является единственным однозначным правильным использованием ссылки (на практике ручное разыменование требуется только там, где ссылка хранится или передаётся функциям).
fn sum(a: &i64, b: &i64) -> i64 {
return a + b
// Или с ручным разыменованием:
return *a + *b
}
fn test() {
let a = 1
let b = 2
let c = sum(&a, &b)
}
Для изменяемых ссылок на структуры необходимо заключить разыменование в круглые скобки, чтобы получить доступ к полю:
struct Foo {
x: i64
}
fn zero_out(foo: &mut Foo) {
foo.x = 0
// Или с ручным разыменованием:
(*foo).x = 0
}
Список функций ссылок (первая версия):
— ссылочные типы;
— параметры ссылочных функций;
— локальные переменные ссылок с базовым анализом времени жизни;
— отсутствие ссылок в структурах;
— отсутствие ссылок в типах возвращаемых значений;
— невозможность изменения ссылок на неизменяемые значения;
— разрешение &foo
и &mut foo
без метки аргумента для параметров с именем foo
;
— автоматическое разыменование ссылок там, где применимо.
Что нужно сделать:
— ссылки (unsafe
) и необработанные указатели, взаимно конвертируемые;
— захват по ссылке в постоянных замыканиях отсутствует.
Замыкания (список функций первой версии):
— функция как параметр функции; — функции как переменные; — отсутствие возвращающих функций из функций; — лямбды могут вызывать исключения; — явные захваты.
Что нужно сделать:
— возврат функции из функции.
Выполнение во время компиляции
Выполнение функции во время компиляции (или CTFE) в Jakt позволяет выполнять любую функцию jakt во время компиляции при условии, что результат значения может быть синтезирован с использованием его полей — в настоящее время это запрещает только несколько объектов прелюдии, которые нельзя создать с помощью их полей (например, объекты Iterator и StringBuilders).
Любую обычную функцию Jakt можно превратить в функцию времени компиляции, заменив ключевое слово function
в её определении на compiletime
. Объявление с ключевым словом comptime заставит все вызовы этой конкретной функции выполняться во время компиляции.
Функции comptime могут вызываться только постоянными выражениями; это ограничение включает объект this методов.
Бросок ведёт себя так же, как и обычный поток управления ошибками, если ошибка выходит из контекста comptime (достигая исходного места вызова), она будет преобразована в ошибку компиляции.
В настоящее время все функции прелюдии с побочными эффектами ведут себя так же, как и во время выполнения. Это позволяет, например, включать файлы в двоичный файл; некоторые функции позже могут быть изменены для выполнения более полезных действий.
Можно разработать собственную обработку импорта на основе данных, доступных во время компиляции. Отличным примером этого в компиляторе Jakt является модуль платформы.
См. меньший пример в образце импорта comptime.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )