LINUX.ORG.RU

Релиз Wasmer 1.0

 , wasmer, ,


1

1

Выпущен Wasmer 1.0 — среда выполнения WebAssembly (сокр. Wasm), написанная на Rust. Wasm автоматически помещает приложения в песочницу для безопасного выполнения, защищая хост от ошибок и уязвимостей в них. Wasm также предоставляет экономичную среду выполнения, позволяющую контейнерам Wasmer работать там, где контейнеры Docker слишком громоздки.

Особенности выпуска:

  • Параллельная компиляция в разы уменьшила время компиляции программ.
  • Компиляторы на выбор: Singlepass (упор на быстроту компиляции), Cranelift, LLVM (упор на оптимизацию кода).
  • Появление кросс-компиляции, возможность скомпилировать Wasm для aarch64 архитектуры с x86_64 машины и наоборот.
  • Поддержка Apple Silicon.

>>> Подробности

★★★★

Проверено: Shaman007 ()

возможность скомпилировать Wasm для aarch64 архитектуры с x86_64 машины и наоборот

Чего? WASM же архитектуронезависимый.

WitcherGeralt ★★
()
Ответ на: комментарий от vertexua

Нет, указанием владения. Потом компилятор сам вычисляет где поставить free.

Чем принципиально отличается от подсчета ссылок с проверкой в компайл-тайме? И я очень сомневаюсь, что все можно проверить на этапе компиляции.

Счетчик ссылок тоде будет, но только в том случае где мы указали несколько owners. Иногда еще лайфтаймы если хочется прямо очень точно все указать.

Чем то, что ты описал, отличается от сборщика мусора, но с другими стратегиями сборки? Счетчик ссылок - это просто самый примитивный вариант gc.

alienclaster ★★★
()
Ответ на: комментарий от vertexua

Сам же гугл и пытается впихнуть мне рекламу на ютубе, даже при том, что у меня оформлена подписка. Не надо этих дебильных рассуждений про победу разума в мире парашников и ушлёпков, толкающих рекламу, этому не быть.

Вспомнилась недавняя новость о том как мошенник оформил на какую-то бабёнку кредит, но деньги взял себе не все, на часть из них он закрыл её старые кредиты.

WitcherGeralt ★★
()
Ответ на: комментарий от vertexua

И при этом ты, вроде бы, неплохо относишься к TS, если я не путаю.

WitcherGeralt ★★
()
Ответ на: комментарий от alienclaster

И я очень сомневаюсь, что все можно проверить на этапе компиляции.

Почитай про Rust что-ли.

Счетчик ссылок - это просто самый примитивный вариант gc.

В Python он всегда, но например в Rust нужно напрямую просить сделать счетчик ссылок.

let x = Rc::new(4);

Иначе будет move-semantics, но не такая дырявая как в С++

vertexua ★★★★★
()
Ответ на: комментарий от vertexua

Почитай про Rust что-ли.

А где он неправ? Любое нетривиальное использование памяти в расте только через unsafe, потому что компилятор не справляется.

но например в Rust нужно напрямую просить сделать счетчик ссылок

…который нельзя реализовать без unsafe, потому что компилятор не справляется. Никакой семантики счетчика ссылок компилятор не видит, для него это просто указатель с delete по условию.

Иначе будет move-semantics, но не такая дырявая как в С++

А расскажите-ка про дыры move-semantics в С++. Не забудьте рассказать про то, как интересно в расте реализовыватся insert if not exists в хэшмапе.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

А где он неправ? Любое нетривиальное использование памяти в расте только через unsafe, потому что компилятор не справляется.

4.2

Не забудьте рассказать про то, как интересно в расте реализовыватся insert if not exists в хэшмапе.

Что там не так?

vertexua ★★★★★
()
Последнее исправление: vertexua (всего исправлений: 1)
Ответ на: комментарий от Siborgium

А расскажите-ка про дыры move-semantics в С++.

Самая паршивая дыра это то что компилятор никак ни контролирует обращение к уже перемещеному «зомби» объекту.

anonymous
()
Ответ на: комментарий от vertexua

4.2

Не порите чушь, ей больно.

fn foo(a: &mut [i32]) {
    assert!(a.len() > 10);
    let b = &mut a[5];
    for i in 1..4 {
        a[i] = 42;
    } 
    *b = 42;
} 
error[E0506]: cannot assign to `a[_]` because it is borrowed
 --> main.rs:6:9
  |
4 |     let b = &mut a[5];
  |             --------- borrow of `a[_]` occurs here
5 |     for i in 1..4 {
6 |         a[i] = 42;
  |         ^^^^^^^^^ assignment to borrowed `a[_]` occurs here
7 |     }
8 |     *b = 42;
  |     ------- borrow later used here

error: aborting due to previous error

Что там не так?

Невозможность conditional move приводит к интересным результатам.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Прикинь, можно вот этого всего не делать и просто в цикле 1..5 назначить 5.

Все искусственные примеры просто переписываются из лапши в другой код. Хотя плюсовики будут говорить «нененене, я хочу чтобы компилятор вот такой мой выкрутас порешал».

В данном случае в компилятор могли бы вставить частный случай чтобы он понимал конкретные индексы, ок, фича возможна. Только если вместо a[5] вставить то что нужно в реальном коде - a[i] где i аргумент функции, то все разваливается и нужно городить еще более сложный компилятор. Это просто никому не нужно

vertexua ★★★★★
()
Ответ на: комментарий от vertexua

Прикинь, можно вот этого всего не делать и просто в цикле 1..5 назначить 5.

Вы же понимаете, что это игрушечный пример, так?

ок, фича возможна

то все разваливается и нужно городить еще более сложный компилятор.

Ну конечно разваливается, потому что это говно примитивное. Все индексы известны в компайл-тайме, он не может посчитать, что куда обращаться будет. И для рантайма можно было бы посчитать, это не рокет сайнс, а довольно простой проход оптимизации. Только вот оптимизацию за rustc делает ллвм, а сами они подобный анализ не осилили. Уже и на polonius мигрировали, а все еще не осилили. Жаль.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от anonymous

Потому что объект находится в «valid, but unspecified state», и обращаться к нему в целом-то можно. Те же moved-from стринги, например, ничем от обычной пустой строки не отличаются.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Потому что объект находится в «valid, but unspecified state», и обращаться к нему в целом-то можно.

Читать из такого объекта нельзя, можно только присвоить новое значение. В целом он абсолютно бесполезен. И было бы лучше чтобы компилятор не давал к нему больше доступа, но в С++ так сделать уже невозможно слишком много старого кода сломается.

Те же moved-from стринги, например, ничем от обычной пустой строки не отличаются.

Нет никакой гарантии что там будет пустой объект, часто пользователькие move конструкторы вообще через swap реализованы, в резултате там будет мусор.

anonymous
()
Ответ на: комментарий от Legioner

правильней было бы выковырять движок из хрома, как это сделал node и его использовать

node ничего не выковыривал, v8 - это компонент хрома, его не надо выковыривать, он уже существует как компонент. внезапно, wasm в нём тоже есть. т.е. даже ничего делать не надо - берёшь и используешь v8.

это какие-то васяны опять переписывают что-то на обосрасте.

anonymous
()
Ответ на: комментарий от anonymous

Читать из такого объекта нельзя, можно только присвоить новое значение.

struct S {
    int i;
    constexpr S(S&& s): i{ s.i } {
        s.i = 42;
    }
};

int main() {
    S a{ 0 };
    S b{ std::move(a) };
    std::cout << a.i << '\n';   
}

Из a нельзя читать потому что ты лично запретил, или что?

Нет никакой гарантии что там будет пустой объект,

Что к чему, какие гарантии, какой пустой объект, при чем тут move? Гарантии тебе дает автор кода.

часто пользователькие move конструкторы вообще через swap реализованы

Мне без разницы, как они реализованы. Если я знаю, что я могу так сделать, то я имею право так делать.

И было бы лучше чтобы компилятор не давал к нему больше доступа,

for (std::string buf; std::getline(buf, std::cout);) {
    foo(std::move(buf));
}

Это, конечно же, другое?

Siborgium ★★★★★
()
Ответ на: комментарий от anonymous

это какие-то васяны опять переписывают что-то на обосрасте.

И правильно делают. Пока есть несколько соревнующихся реализаций, они будут как-то развиваться. Что бывает, когда их нет, можно наглядно видеть на рынке браузеров.

Siborgium ★★★★★
()
Ответ на: комментарий от WitcherGeralt

Не знаю. Не пользуюсь этой функцией.

mydibyje ★★★★
() автор топика
Ответ на: комментарий от anonymous

В node нет песочницы, поэтому я так и написал. Если нужен wasm, хочется как-то ограничить возможность скрипта взаимодействовать с внешним миром. Хотя не исследовал сильно эту тему, возможно я не прав и в ноде можно сделать нормальную песочницу.

Legioner ★★★★★
()
Ответ на: комментарий от Siborgium

ну какбы и так понятно чем это закончится: будут неисправляемые утечки памяти потому что «обосраст не течёт» и надо будет всё опять переписывать.

anonymous
()
Ответ на: комментарий от Legioner

я не понимаю, что мешает сделать песочницу снаружи инстанса ноды, зачем интегрировать что-то в прямую кишку?

anonymous
()
Ответ на: комментарий от vertexua

И я очень сомневаюсь, что все можно проверить на этапе компиляции.

Почитай про Rust что-ли.

А при чем тут раст? Смотри, тебе уже в рантайме прилетает код на расте, допустим, который надо выполнить (из файла, например). Как ты на этапе компиляции своей программы вычислишь, где в заведомо неизвестном коде надо расставить инструкции по освобождению памяти? Или у раста такой компилятор, что там есть репл и он автоматом может формировать по сути стратегию сборки мусора? Я не видел еще ни одной реализации ни одного языка статически типизированного, где бы это работало. В теории - это возможно. Но на практике мы имеем сборщики мусора или у твоя программа должна быть достаточно простенькой, то есть статически доказуемой на предмет того, что там вообще возможно в конкретных местах расставить подобные инструкции.

Счетчик ссылок - это просто самый примитивный вариант gc.

В Python он всегда, но например в Rust нужно напрямую просить сделать счетчик ссылок.

Вопрос в том, что выше ты говорил, что 1) «без gc» и 2) на ответ анонимоуса про «подсчет ссылок» ты сказал «нет». Хочу понять, что ты имел ввиду.

alienclaster ★★★
()
Ответ на: комментарий от alienclaster

Почему задача стала что мне вдруг нужно компилировать на лету неизвестный код? Можем мне ещё укусить себя за локоть стоя на одной ноге? Хочешь как в лиспе - пиши на Лиспе.

У меня есть известный код в .rs. Там нету ни такого как в Java где я не думаю о памяти, но потом рантайм делает кульбиты, угадывает что я имел ввиду, делает mark and sweep, что-то перемещает в памяти. Там также нету такого как в Python, где мне в глотку пихают счётчик ссылок, хотя у меня один объект и одна ссылка на него в 80% кода. Там нету такого как в С, где я должен вызывать free сам. Там нету такого как в С++, где дожили до RAII, деструкторов, move semantics, к счастью, но во всем этом легаси мусорка и легко где-то накосячить в синтаксисе конструктора, вызвать не тот, куда-то впихнуть null, где-то сделать дважды delete.

Тут Rust - язык со строгим описанием типа владения к каждой переменной. Только для чтения или нет? Нужен многопоточный доступ или нет? Сколько владельцев? Укажите комбинацию вышеперечисленного и мы правильно вычислим где м когда сделать free и статически проставим это на этапе компиляции. И даже подскажем что какой-то режим оверкилл, вы указали, но ее используете его мощность.

Это ближе всего к очень современному С++ и главное очень современному стилю кода в С++ у опытного программиста. Но сам язык сделан так чтобы обычный код был аналогом вышеописанного уровня кода С++. Написать плохо намного сложнее

vertexua ★★★★★
()
Последнее исправление: vertexua (всего исправлений: 2)
Ответ на: комментарий от ma1uta

Мы уже выяснили, что США и Австрия - банановые государства, чьи служащие не понимают в безопасности в отличии от.

Имхо глупо выдавать местечковый банк в Техасе и его ошибки за государственные.

Ведь государство уже ~ 10 лет требует отказаться от аплетов так как они небезопасны.

Но вы похоже так поверили в глупость что повтряете ее в третий раз.

Забавно ;)

grim ★★☆☆
()
Последнее исправление: grim (всего исправлений: 2)
Ответ на: комментарий от vertexua

Почему задача стала что мне вдруг нужно компилировать на лету неизвестный код?

Просто хочу понять, какого уровня инструменты простановки автоматического освобождения памяти на этапе компиляции наличествуют в расте.

угадывает что я имел ввиду, делает mark and sweep, что-то перемещает в памяти

Т.е. после продолжительного времени интенсивной работы с памятью она будет фрагментирована и в лучшем случае все начнет «немножечко тормозить»?

Там также нету такого как в Python, где мне в глотку пихают счётчик ссылок, хотя у меня один объект и одна ссылка на него в 80% кода.

Самый примитивный сборщик мусора - подсчет ссылок. Из того примера, что ты приводил выше - в расте тоже используется подобная техника (как и в С++). В расте так происходит в случае, когда не справляется штатное средство для простановки аналога «free»? Или нафига тебе Rc (как я понял это и есть reference counting), если компилятор везде статически может нафигачить правильных «free» в нужных местах?

Там нету такого как в С, где я должен вызывать free сам.

Одно дело должен, другое можешь. В расте ты можешь сам освободить память вручную? А если попробуешь сделать это несколько раз для одной и той же области памяти - это будет ошибка компиляции? А если не можешь, то почему? Компилятор умнее?

If you really want to allocate memory and get a raw pointer to it, you can look at the implementation of Rc. (Not Box. Box is magical.)

Цитата из ответа на SO на запрос «rust manual memory management». Звучит пугающе.

Там нету такого как в С++, где дожили до RAII, деструкторов, move semantics

В том или ином виде это присутствует везде, где работают с ресурсами. Просто кое-где оно сделано плохо.

Тут Rust - язык со строгим описанием типа владения к каждой переменной. Только для чтения или нет? Нужен многопоточный доступ или нет? Сколько владельцев? Укажите комбинацию вышеперечисленного и мы правильно вычислим где м когда сделать free

Первые два юзкейса похожи на контекст для оптимизации, неясно какое они имеют отношение к стратегии освобождения памяти? А на счет «сколько владельцев» это ты сам должен бегать по коду и всех их искать? Если нет, то зачем тогда указывать сколько владельцев (числом ведь просто не отделаешься, наверняка надо указывать *что* это за владельцы поименно)? Это такая своеобразная замена delete для ностальгирующих? :)

И даже подскажем что какой-то режим оверкилл, вы указали, но ее используете его мощность.

Даст варнинг со ссылкой на документацию и rust best recipes? :)

Это ближе всего к очень современному С++ и главное очень современному стилю кода в С++ у опытного программиста.

Так в С++ опытные программисты как раз и используют подсчет ссылок (в разных вариациях). Альтернатива - ручной new / delete. И современный С++ с подсчетом ссылок «тормозит» (но безопасней). Указание «владельцев» это имхо как максимум немного лучше журналистского расследования исходников в поисках мест для простановки delete. Если раст сам этих владельцев может идентифицировать и расставить на этапе компиляции все необходимые инструкции для освобождения ресурсов (включая память), тогда это гуд. Но может ли он так? И если да, зачем их тогда указывать?

alienclaster ★★★
()
Ответ на: комментарий от Siborgium

error[E0506]: cannot assign to `a[_]` because it is borrowed

Весело там у вас :) Но теоретически ведь тут нет неразрешимой задачи в рамках «парадигмы владения» раста?

Невозможность conditional move приводит к интересным результатам.

https://doc.rust-lang.org/src/std/collections/hash/map.rs.html#2215-2220 - на сколько я понял, речь об этой реализации? С растом знаком поверхностно, но выглядит, вроде бы, нормально, или там есть какие-то подводные камни?

alienclaster ★★★
()
Ответ на: комментарий от vertexua

Только если вместо a[5] вставить то что нужно в реальном коде - a где i аргумент функции, то все разваливается и нужно городить еще более сложный компилятор. Это просто никому не нужно

Понимаешь, это тупо простейший случай вообще, который только можно придумать. И даже оно уже «ненужно». А я тебя начал спрашивать о компилировании файлов и выводе типов в рантайме... ?!#$%. Извини, я был неправ :)

alienclaster ★★★
()
Ответ на: комментарий от alienclaster

Речь о части одной глобальной проблемы с move в расте. Код по ссылке прячет реализацию в hashbrown::hash_map as base;, никакой реализации реально там не приведено.

В расте move осуществляется всегда. Я не могу написать код вида

let v: V = ...;
let k: K = ...;
let inserted = map.insert(&k, v);
if !inserted {
    foo(v);
}

потому что владение v безусловно передается в insert. Расту приходится обходить это повсеместно, возвращая v обратно в случае неудачи. Это, мягко говоря, субоптимальное решение. Решения этой проблемы без unsafe не существует – это ограничение базовой модели.

Нельзя осуществлять доступ к глобальным переменным без unsafe. Это ограничение бывает оправдано, но никакого анализа не осуществляется, и абсолютно безопасные случаи оказываются заблокированными. Либо программист анализирует свой код сам и расставляет unsafe там, где он уверен, что все будет в порядке, либо… Заворачивает все в написанные поверх unsafe чьи-то обертки.

И так далее, и так далее. Rc, кстати, нельзя написать без unsafe.

struct Rc<T> { 
    t: T, 
    c: u32, 
} 

impl<T> Clone for Rc<T> {
    fn clone(r: &Self) { 
        // r иммутабельный, не могу увеличить счетчик ссылок 
    } 
} 

Хотя @vertexua, конечно, прав – сделать плохо там сильно сложнее, особенно программисту, мало знакомому с низкоуровневыми языками. Сделать хорошо, впрочем, тоже.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от alienclaster

Так в С++ опытные программисты как раз и используют подсчет ссылок (в разных вариациях).

Нет в C++ (раст аналогично) подсчет ссылок используется только тогда, когда другие способы не подходят. Основной способ это стековые объекты, время жизни которых управляется RAII, они могут выделять память и в куче (типичный пример std::vector), но никакого подсчета ссылок не используют. Введение в C++ move семантики сильно удешивило использование таких объектов. Если нужны полиморфные объекты то для них основной умный указатель это std::unique_ptr (Box rust) который тоже не использует счетчик ссылок.

anonymous
()
Ответ на: комментарий от Siborgium

В расте move осуществляется всегда. Я не могу написать код вида.
потому что владение v безусловно передается в insert. Расту приходится обходить это повсеместно, возвращая v обратно в случае неудачи. Это, мягко говоря, субоптимальное решение. Решения этой проблемы без unsafe не существует – это ограничение базовой модели.

Решение простое, тупо вручную делается clone. В С++ как раз в таких случаеях по умолчанию тоже будет копирование. Если же копирование дорого, то в расте в тех же HashMap и ко. есть вполне удобный entry.

anonymous
()
Ответ на: комментарий от Siborgium

Хотя @vertexua, конечно, прав – сделать плохо там сильно сложнее, особенно программисту, мало знакомому с низкоуровневыми языками. Сделать хорошо, впрочем, тоже.

Сделать хорошо и без unsafe, в большинстве случаев тоже возможно, но для этого надо неплохо изучить стандартную библиотеку, там для 99% проблем есть готовое решение. Но да объем того что нужно изучить очень приличный и это поднимает планку входа в раст.

anonymous
()
Ответ на: комментарий от Siborgium

В расте move осуществляется всегда

Если хочешь плюсовую семантику с передачей по ссылке, то бери и передавай по ссылке. В чем проблема? Специально для таких как ты есть std::mem::take, который плюсовую мув семантику эмулирует.

Rc, кстати, нельзя написать без unsafe.

Можно, если опираться на готовые примитивы из стандартной библиотеки (см std::cell::Cell). Они, конечно, написаны на unsafe, но кого это волнует?

Oxd34d10cc
()
Ответ на: комментарий от anonymous

Каким образом? Виртуалка это слишком тяжеловесно. Контейнер не считается безопасным подходом для изоляции. Да и тоже не так легковесно, как хотелось бы. Представь, что на wasm заливается функция на shared server, которая может вызываться сотни раз в секунду (микросервис). Тут даже процесс стартовать будет тяжеловесно.

Legioner ★★★★★
()
Последнее исправление: Legioner (всего исправлений: 1)
Ответ на: комментарий от Legioner

Контейнер не считается безопасным подходом для изоляции.

Какой контейнер, кем не считается?

Представь, что на wasm заливается функция на shared server, которая может вызываться сотни раз в секунду (микросервис). Тут даже процесс стартовать будет тяжеловесно.

man seccomp

anonymous
()
Ответ на: комментарий от anonymous

Ну тогда в си тоже счетчик ссылок. Программист же в уме посчитал.

vlad9486
()
Последнее исправление: vlad9486 (всего исправлений: 1)
Ответ на: комментарий от Siborgium

Rust - это игра про выживание в открытом мире с элементами жанров action/adventure и RPG. Мы не одни в этом мире. Кроме нас здесь существуют люди и животные. Если с первыми все понятно, то с животными все поинтересней. Разработчики в своей игре хотят больше уделить внимание природе. Здесь вы встретите и кроликов, и медведей, и волков.

anonymous
()
Ответ на: комментарий от anonymous

Основной способ это стековые объекты, время жизни которых управляется RAII, они могут выделять память и в куче (типичный пример std::vector), но никакого подсчета ссылок не используют.

Так это понятно, там контекст был, что используется в случаях, когда стековых объектов не хватает.

Если нужны полиморфные объекты то для них основной умный указатель это std::unique_ptr (Box rust) который тоже не использует счетчик ссылок.

Я давно С++ не смотрел, возможно что-то изменилось. Но как минимум один из самых популярных использует подсчет ссылок (если неправ, поправьте) это std::shared_ptr. std::unique_ptr работает на move-семантике, естественно, там нет подсчета ссылок (считать нечего, уникальное владение). Но это самый примитивный пример. А когда время жизни и стратегия владения посложнее - что приходит на помощь? Подсчет ссылок. weak_ptr тоже нужен для управления подсчетом ссылок (решение проблем shared_ptr). scoped_ptr еще есть - управление внутри скоупа, даже move-семантика запрещена. Есть еще какие-то стратегии умного управления памятью в С++? В расте все сложнее, чем комбинация этих четырех умных указателей?

alienclaster ★★★
()
Ответ на: комментарий от alienclaster

Т.е. после продолжительного времени интенсивной работы с памятью она будет фрагментирована и в лучшем случае все начнет «немножечко тормозить»?

То, что это прямо большая проблема придумали разрабочики сборщиков мусора чтобы сказать «аха, мы вот тут сжираем горы тактов CPU, но вот есть теоретическое преимущество - мы дефрагментируем память, опа». На деле что-то не очень известно чтобы это была большая проблема, даже простейший slab allocator будет по кругу циркулировать обьекты похожего размера.

В расте так происходит в случае, когда не справляется штатное средство для простановки аналога «free»?

В расте можно написать вот так

let a = A{};
f1(a);
f2(a);

Из-за того что по дефолту в расте происходит перемещение, то этот код не скомпилируется, a полностью ушла в f1. Тут есть несколько вариантов.

Одолжить. Компилятор посмотрит глубоко внутрь f1 чтобы там не было перемещений в свою очередь, только копирования или одалживание:

f1(&a);
f2(&a);

Разрешить копировать A неявно:

#[derive(Copy)]
struct A{}

Разрешить копировать A явно - клонировать.

#[derive(Copy)]
struct A{}

let a = A{};
f1(a.clone());
f2(a);

И у копирования и у клонирования может быть ручная реализация, те атрибуты просто генерируют реализацию по умолчанию.

Можно применить счетчик ссылок. Сам счетчик поддерживает клонирование, освобождая сам обьект внутри него от необходимости клонироваться. Rc - не содержит атомарных инструкций и не может быть использован в многопоточном коде. Если например f1 или f2 внутри передают a в другой поток - компилятор это увидит и код не соберется. Все обьекты которые поддерживают многопоточность строго помечены специальными traits, без которых нельзя проникнуть между границами потоков. Rc - не помечен.

let a = Rc::new(A{});
f1(a.clone());
f2(a);

Для многопоточного случая есть счетчик ссылок с атомарным счетчиком.

let a = Arc::new(A{});
f1(a.clone());
f2(a);

Он позволит передать read-only копию в разные потоки. Все равно, мутировать сам a не получится. Дело в том что все это время тип a был A, а не mut A.

Для этого нужен Mutex или RwLock. В Rust Mutex - контейнер, а не независимый обьект. Он хранит в себе то, что защищает.

let a = Arc::new(Mutex::new(A{x:1}));
f1(a.clone());
f2(a);

Мы тут комбинируем возможность подсчитывать ссылки через Arc и получать доступ для записи через Mutex.

Потом где-то в дебрях f1 мы хотим изменить общую для всех копию a.

{
   let a_value = a.lock(); // lock() возвращает мутабельную ссылку `&mut A` и закрывается по выходу из блока.
   a_value.x += 1;
}

Нигде у вышеописаного кода нету явных освобождений памяти. При этом в машинном коде в итоге внутри некоторых функций, где компилятор доказывает выход обьекта out of scope статически проставляется free. Иногда для счетчика ссылок.

Гарантии насчет мутабельности и многопоточности не просто равны таковым в Java, но еще и превосходят их. Java например не заставила разработчика использовать синхронизацию, обнаружив многопоточную работу. Только в рантайме некоторые коллекции пугаются и что-то там детектируют, бросая ConcurrentModificationException.

В расте ты можешь сам освободить память вручную?

Функция освобождения памяти unsafe. Значит ее можно выполнить внутри unsafe блока. Такой блок можно написать, но он очень заметен во время ревью и по сути означает что программист сознательно делает что-то интересное с памятью. Язык программирования должен помогать ровно до того момента пока он помогает. Когда он мешает, тогда он должен отойти в сторонку. К счастью Rust в основном помогает. Там где он мешает и происходит что-то необычное можно просто написать unsafe и все обмазать комментариями почему на самом деле это safe просто мы делаем low-level манипуляции, безопасность которых компилятор доказать не может и «ошибается» в сторону «не пущать».

vertexua ★★★★★
()
Последнее исправление: vertexua (всего исправлений: 1)
Ответ на: комментарий от anonymous

Так в С++ опытные программисты как раз и используют подсчет ссылок (в разных вариациях).

Нет в C++ (раст аналогично) подсчет ссылок используется только тогда, когда другие способы не подходят.

Если что, я не хотел сказать, что в С++ подсчет ссылок используется прям _везде_ (как в питоне). Но только «когда другие способы не подходят» - а другие способы это стековые объекты, мув семантика и умный указатель, привязанный к скоупу. Ничего не забыл? То есть в реальных сложных приложениях будет много shared_ptr в коде со всеми вытекающими.

alienclaster ★★★
()
Ответ на: комментарий от alienclaster

Если нет, то зачем тогда указывать сколько владельцев

Как я указал выше - указываешь режим: единоличное владение, временное одалживание у владельца, множественное владение когда по оси времени порядок «удаления» владельцев не предопределен.

fn f1(a: &A) {
  // Может смотреть в a, но не может ее забрать куда-то где будет в итоге удаление.
}

fn f2(a: A) {
  // Забрал a, удаление компилятор проставит в конце этой функции
}

let x = A{};

f1(&x);

// x вернули из f1.

f2(x);

// x тут уже не доступен, его всосало в f2. free тут не будет. 

И современный С++ с подсчетом ссылок «тормозит» (но безопасней)

Он тормозит на подсчете этих ссылок, но не во время доступа к переменной. Практически мало кода в цикле увеличивают счетчик ссылок. Что я вижу чаще - в 3-4 местах в коде нужен какой-то FooManager и неизвестно кто будет последний ссылаться на него. Следовательно 3-4 раза будет происходит увеличение атомарного счетчика, что практически бесплатно. Внутри же - каждый владелец будет передавать свою ссылку через по-настоящему бесплатное одалживание, которое не волнуется о освобождении.

vertexua ★★★★★
()
Ответ на: комментарий от vertexua

Еще интересная вещь - лайфтаймы. Это самая сложная часть раста. Обычно без нее можно обойтись, но иногда нужно прямо совсем все заоптимизировать. Вернемся к вот этому.

в 3-4 местах в коде нужен какой-то FooManager и неизвестно кто будет последний ссылаться на него

Почему кстати неизвестно? Потому что я ленивый и мне все-равно. Влепил счетчик ссылок и разослал FooManager в какие-то асинхронные лямбды.

Но допустим я знаю что все мое приложение существует внутри какой-то одной структуры которая существует дольше всего в моем приложении. Например так же как IoC контейнер в Java вроде Spring ApplicationContext.

В свои стуктуры я могу указать lifetime как шаблонный параметр, которым могу пользоваться для ссылок внутри этой стуктуры, доказывая что эти ссылки живут дольше чем моя структура, значит ей не нужно ими владеть и можно просто одалживать. Это намного круче чем одалживать внутрь функции - это хранить в структуре ссылку бесплатно и знать что собственник ее не уничтожит.


struct Client<'a> {
  x: &'a FooManager;
}

impl Client {
  fn use_x(self) {
    self.x.call();
  }
}

Client живет время 'a, а тот обьект на который ссылается «x» гарантировано живет дольше чем Client, им владеет кто угодно другой, но живет он как минимум нужное время.

Это накладывает сразу ограничения на весь код, который создает FooManager и Client, но главная фича тут в том, что это не забота Client об этом парится. Ссылка, нет владения, нет счетчика, полная бесплатность. Это все работает в безумных многопоточных приложениях, потому что на каждом шагу компилятор следит за временами жизни на этапе компиляции. Но в простом примере вот как.

let fm = FooManager{};

let client = Client{x: &fm}; // fm живет дольше client.

// Сначала невидимое освобождение client, только потом FooManager.

Лайфтаймы еще помогают в сигнатурах функций

fn process(a &str, b: &str) -> &str {
...
}

Тут не совсем ясно, мы одалживаем две строки, и возвращаем тоже ссылку на какую-то одолженую строку… Еее явно не создали внутри, так как все собственники внутри умерли по выходу из фунции. Стало быть это a, b или их подстроки. Но какие?

fn process<'x>(a &'x str, b: &str) -> &'x str {
...
}

Все, расходимся, это кусок a.

Это важно в лапше вроде такой

let a = String::new("a");
let b = String::new("b");
let result = process(&a, &b); 
// Тут мы вроде бы вернули a, b, но секретно ссылаемся на a внутри result.
i_eat_variables(a); // Ошибка компиляции. Тут единолично хотят украсть владение над a, переместить ее
i_eat_variables(b); // Кстати вот тут ошибки не было бы.

println("{}", result);

vertexua ★★★★★
()
Последнее исправление: vertexua (всего исправлений: 6)
Ответ на: комментарий от alienclaster

В расте все сложнее, чем комбинация этих четырех умных указателей?

Если кратко, то забанены неумные указатели если не пометить код unsafe, оставлен стек и умные указатели, очень жёстко огородили ссылки, создав серьезный инструментарий как к ссылкам добавить больше информации чтобы тебе компилятор объяснял почему твой код не крут. Так же в эту систему ссылок и указателей добавили взаимосвязь владения, мутабельности и многопоточности в одну систему чтобы впервые компилятор знал ещё и о потоках

vertexua ★★★★★
()
Последнее исправление: vertexua (всего исправлений: 1)
Ответ на: комментарий от alienclaster

То есть в реальных сложных приложениях будет много shared_ptr в коде со всеми вытекающими.

Нет не будет. shared_ptr нужен только тогда когда мы не можем заранее знать время жизни объекта и при этом к нему нужен одновременный доступ из разных мест. В остальных случаех shared_ptr избыточен и даже вреден. Основная масса памяти в типичном С++ приложении будет выделятся в виде стековых обьектов, включая долгоживущие выделенные в main функции.

anonymous
()
Ответ на: комментарий от anonymous

То есть в реальных сложных приложениях будет много shared_ptr в коде со всеми вытекающими.

Нет не будет. shared_ptr нужен только тогда когда мы не можем заранее знать время жизни объекта

Но ведь это достаточно большой класс задач.

alienclaster ★★★
()
Ответ на: комментарий от alienclaster

Если что, я не хотел сказать, что в С++ подсчет ссылок используется прям везде (как в питоне). Но только «когда другие способы не подходят» - а другие способы это стековые объекты, мув семантика и умный указатель, привязанный к скоупу. Ничего не забыл?

Забыл обычные «глупые» указатели и ссылки, их вполне можно безопасно использовать везде где не нужно следить за владением.

anonymous
()
Ответ на: комментарий от anonymous

Забыл обычные «глупые» указатели и ссылки, их вполне можно безопасно использовать везде где не нужно следить за владением.

Мы перечисляли все способы помимо глупых указателей и ссылок.

alienclaster ★★★
()
Ответ на: комментарий от alienclaster

Но ведь это достаточно большой класс задач.

Я бы не сказал. И даже в этом классе, при необходимости (например тормозах) shared_ptr всегда можно заменить на альтернативные способы (например арены памяти).

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.