All streams
Search
Write a publication
Pull to refresh
0
@adjachenkoread⁠-⁠only

User

Send message

Вы вроде какой-то исследовательский проект пилили за спасибо? Похоже деньги заканчиваются, а спасибо чето не очень вкусное и надо идти добывать масло на хлеб. Я бы тоже хотел свой "Раст" запилить, но… Подожду маленько. Вы хоть "отчёт" о своем исследовании опубликуйте, а то и непонятно "стоило" оно того или нет, понятное дело что вам в кайф, но нам то со стороны виднее.

Ну если это правда, а у меня нет причин не верить, тогда сразу +100 к качеству курса. Может ещё расскажите, что помимо того, что вы учите студентов писать с++ код (что в общем то не рокет сайнс) вы ещё и ЧИТАТЬ с++ код учите?

Что я имею ввиду ?- упражнение типа перечислите все ошибки, которые вы можете найти в таком то продакшн учебном коде. Я бы это вообще частью выпускного экзамена сделал, на бумажке (т.к. на код ревью в реальности нет доступа ни к исходникам ни к IDE). Если нашёл меньше минимального количества багов — сразу просто незачёт не взирая ни на что другое.

Есть ещё один навык который я называю умением читать код — перед тем как копипастить со стэка писать свой велосипед нужно сначала поискать, а что уже есть в стандартной библиотеке и/или другом фрэймворке который используется в проекте и наконец у каждого большого проекта есть своя библиотека типа Tools/всякое_полезное и там почти всегда как с попаданием в кэш уже можно найти готовый к употреблению код.

Ну и самое частое люди обычно находят место в коде куда нужно добавить новый функционал и прямо туда его и фигачат сплошной простыней, а то что используются разные уровни абстракции вообще ни каких сомнений не вызывает. Вот пример для наглядности
void implementLotsOfLogicHere()
{
DoHighLevelA();
DoHighLevelB();

// new code added here
Container anotherViewOnTheData;
while (x: container)
{
if(x == ...)
{
anotherViewOnTheData.push_back( Y{ x } );
}
}
callback(anotherViewOnTheData);

// previous code conitnue
DoHighLevelC();
}

И это еще идеально если DoHighLevelX кто-то уже написал, а в реальности там вместо этих функций тоже смесь кода с разной степенью абстракции — в одной строчке зарождение вселенной, а рядом байтики копируется в цикле из одного вектора в другой.

Если вы еще и читать код ваших студентов учите, то это прямо вообще респект и уважуха.

У меня к вам вопрос, а как вы учите студентов пользоваться вектором правильно. В моём мире 100% людей работающих с плюсами (и даже сеньоры) иногда да возьмут да и сохранят указатель/ссылку на элемент вектора, а потом возьмут да и добавят новый элемент в вектор, а потом поработают с ссылкой. Я думаю для вас не секрет, что это довольно часто нормально сработает, но вот иногда все будет печально. Так вот как ваши студенты воспринимают первые встречи с УБ и как вы их учите с ними справляться? Учить с++ и не учить как жить в мире с УБ это пустая трата времени и вашего и студентов, а потом ещё и того парня который будет это все чинить.

А ну т.е. если группа влиятельных бизнесменов (не)лояльных государству будут действовать (против) в русле гос политики по якобы собственной инициативе то это не цензура. Нет уж лучше я сам выберу что мне (не) читать/смотреть, я уже взрослый сам решать.

Ну мне не кажется эта идея не правильной, даже с учётом ЦП и игила. Я может быть не осознаю/вижу всей опасности. По сути это спор о "никакой свободы врагам свободы", терор, ЦП да почти что угодно может быть показано в контексте противостояния свободе с разной степенью усилий/промывки мозгов. Ну а дальше баним врагов свободы. Я лично против, я за конкуренцию идей какими бы дикими они ни были.

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

НЕТ его у меня и я пока не знаю/представляю как его сделать.

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

Я не думал глубоко над тем как оптимизировать переопределение и заменить его на присваивание, это не значит, что это уже кем то не придумано. Я почти уверен, что я вообще ничего нового здесь не придумал и уже есть десятки работ по CS где это уже лет как 30 все разобрано до мелочей в том числе как это оптимизировать, просто как это обычно и бывает ещё не пришёл тот человек/команда который все этого воедино соберёт.

Конечно я всего лишь анонимус из интернета, может я вообще gpt-3 бот, и верить на слово мне нельзя. Если вы убеждены, что такая оптимизация не возможна или представляет собой открытую проблему, то я бы послушал ваши аргументы почему вы так думаете — мне реально интересно.

Вот смотрите моё предсказание направления эволюции раста или его наследника — циклы станут выражениями, как я их здесь описал, присваивание уедет в ансайф (для тех случаев где с автоматической оптимизацией будет туго) со своим спец синтаксисом наподобие что был в паскале :=, let и mut исчезнет. За счёт этого аннотация лайфтайма сильно упроститься будет преимущественно автоматической примерно так же как сейчас type inference. Код будет выглядеть как будто написан на динамическом языке (в примере выше как раз демонстрируется смена типа переменной arr cо статического масива на вектор, но это уже и так доступно), но при этом будет полностью статически типизированным как сейчас.

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

Вот кстати меня это немного обескураживает, если я дичь говорю так перестаньте мне отвечать и/или заодно карму за минусуйте — это логично, дискуссия увянет сама собой и рот мне прикроет заодно. А тут так получается, одна рука (человек) рот затыкает, а другая (может даже те же самые люди, а может и другие) вступают в диалог и ждут ответа. Вот это вообще смысла не имеет. Не переживайте это не лично к вам, это так дань традиции высказать своё фу на систему кармы.
Если нет мутации внешних скопов, то всё, что происходит внутри цикла, остаётся внутри цикла — каждая итерация будет выполняться с теми же данными, как и первая.


Дальше идет псевдо ассемблер:
push state //сохраняем первоначальное состояние цикла в стеке
push condition // сохраяняем счестчик/условие
loop: pop condition
cmp condition 0
jne break
call body
dec condition
push condition
jmp loop
break: ...

body: pop state
work with state localy
push new state
ret


Смотрите здесь почтивсе так же как просиходит в любом языке. Разница в том, что любой язык добавит непосредсвтенно перед безусловным jmp что то типа
pop state
Это будет соответсовать drop, выходу из скоопа/окончанию времени жизни возвращаемого/локального значения из тела цикла. НЕТ никаких технических проблем не делать этот pop state. Да синтаксиса это выразить нет, ну так это не моя беда.
Самое близкое что есть в расте это
let x = loop{
break value;
}

Смотрите опять же циклы в расте НЕ выражения — т.е. что бы что то вернуть из цикла нужно вызвать break со значением. То что я написал на псевдо асме делает цикл выражением таким же как if, но возвращая значение на каждой итерации. Последнее значение может быть присвоенно внешней переменной, а промежуточные значения переходят на следующую итерации. Т.е еще раз это механика фолд через цикл + один уровень стека. Вобщем то он так и реализован в любом языке, только через явное присваивание state на каждой итерации. Таким образом хвоствая рекурсия которая нуждается только в одно уровне стека и никогда не приведет к SO, сама функция не вызавает сама себя, а возвращает результат для следующего вызова на стеке и уже внешний цикл делает этот вызов. Да только хвостовая рекурсия может быть так записана (т.к. в общем случае потребуется более одного уровня стека), но так это именно то во что конвертируются циклы. В любом случае я показал на этом примере как именно низкоуровневая механика ЦИКЛА, а не механика рекурсивного вызова прекрасно сочетается с переопределением вместо присваивания.

Я бы предложил например такой синтаксис:
let x = 0;
let x = for let sum = x, value in (0..10) {
    let sum = sum + value // последняя итерация вернет значени на верхний скоуп, промежуточные итерации переопредялют sum для следующей итерации
}
println!({}, x); // должно печатать 55

а без let выглядит еще проще
result = for sum = 0, value in (0..10) {
    sum = sum + value
}

Думаю можно и получше синтаксис придумать, это не суть. Суть в том, что любой цикл я вам через fold выражу, а этот фолд потом в цикл и преобразую. Синтаксис при этом будет как у родного ванильного цикла. К if выражениям уже все привыкли и оценили их по достоинству, почему циклы не сделали выражениеми — загадка.
Да вы правы, ещё раз перечитал, я ещё довольно таки плаваю в основах ФП и могу нести не то что есть на самом деле, спасибо за поправку. Мой мохзг запомнил «главное» то что захотел, а не то что есть category-theory-for-programmers 2.6
You can define a function that takes
Void, but you can never call it. To call it, you would have to provide
a value of the type Void, and there just aren’t any.
Ещё раз, другими словами — ЛЮБОЙ цикл заменяется на рекурсию. ЛЮБАЯ рекурсия заменяется на цикл, хвостовая особый случай и заменяется эффективно. Если вы с этим не согласны, мне больше нечего вам сказать — учите мат. часть. Если вы с этим согласны, тогда вспоминаем мой первоначальный тезис — на КОНЦЕПТУАЛЬНОМ уровне (т.е. игнорируя детали реализации в том числе производительность) присваивание и определение это одно и то же.
Я не знаю, может у вас свой персональный компилятор раста, но тот который в песочнице говорит error[E0282]: type annotations needed
Я нигде не вижу никаких {unknown}.

Ну вот и {unknown} такой же — ВНУТРЕННЕЕ обобщенное(не доконца выведенное) представление типа любой переменной. Которе является деталью реализации компилятора и НИКОГДА не доступно из программы


Противоречит вашему предыдущему ответу, что на консоль в качестве имени типа будет распечатано {unkown}. Тут либо на консоль пойдёт что то другое либо оно доступно из программы.

А сам факт того что оно не компилируется + текст ошибки говорит о том, что определение переменной x неполное. Я до сих пор не пойму о чем тут можно спорить дальше.

В помощь вам простая модификация моего примера, сделайте то о чем просит компилятор — добавьте аннотацию типа например let x: i32; И тогда следующая ошибка будет borrow of possibly-uninitialized variable: `x`

Видите — тип уже есть (адрес думаю тоже), но вот валидного значения все ещё нет — незаконченное определение. Если вы пуститесь во все тяжкие используете unsafe, то тогда требование валидного значения снимается и там же в ансэйфе вам будет доступен адрес. Но даже для ансэйф нужен тип.

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

Я не могу представить интерфейс {unknown} — он пуст /бесполезен. Даже Void (тип с монозначением) более полезен чем ваш {unknown}. Ваш тип {unknown} не имеет значений — пустой. Из основ ФП я знаю, что нет ни одной функции из пустого типа во что бы то ни было поэтому он бесполезен на практике.
Я рад что ваша риторика изменилась, с «без let ну вообще жизни нет никак» на «дайте мне тоже самое, но с перламутровыми пуговицами»
Покажите, как можно переписать этот код без mut и дополнительных аллокаций


Я изначально сказал, что на КОНЦЕПТУАЛЬНОМ уровне присваивание и (пере)определение это одно и тоже, но присваивание это узаконенная форма оптимизации (которая очень важна). Так же я говорил, что только лишь раст имеет возможность переложить эту оптимизацию с человека на компилятор, а другие языке не могут в принципе, но я нигде не говорил что раст УЖЕ реализовал все необходимые оптимизации. Это раз.

Зная основы ФП, даже не глядя на ваш код я уверен, что он может быть переписан без единого присваивания на расте, но вот ваше требование отсутствия дополнительных аллокаций (за базу для сравнения очевидно берем вашу реализацию) обоснованно примерно чуть более чем никак. Это два.

Ну и самое главное, каникулы закончились времени на дискуссии/упражнения в раст у меня больше нет, так что вам для саморазвития будет полезно самому попытаться переписать ваш собственный пример, для начало запилите все присваивания, а уже потом смотрите сколько там аллокаций может раст уже все сам разрулил, такое часто бывает, иногда бывает даже так что ФП подход приводит даже к БОЛЕЕ эффективному коду чем классика. Я вполне допускаю, что без доп аллокаций сегодня это переписать невозможно, но вы не отчаивайтесь, с каждым релизом раста эффективность будет расти.

«НАСТОЯЩИЙ ПРОГРАММИСТ НИКОГДА ТАКОГО НЕ НАПИШЕТ»
я такого не писал, я сказал что пример выше бессмысленная белиберда, полезная разве что только в качестве упражнения в ренджи, и поэтому насколько (не)эффективный код раст сгенерирует для этого примера меня лично вообще не интересует. Ваш пример куда разумней, но времени нет.
Я ничего не игнорирую, биг-бадабум абракадбра пышь оригинальный цикл который эквивалентен примру ищи выще по уровню. Это просто хабр имеет не самый удобный интерфейс комментариев да и вас таких много, а дартаньян я один, тут лето в разгаре и новогодние каникулы уже закончились.

Невозможное возможно, если немного почитать основы ФП, я там ничего нового не изобрёл. Как видите никаких рекурсий, хотя наложение ограничения на рекурсию высосано из пальца ничем не обоснованно.
Да вы конечно правы, мой предыдущий пример не о том. Я начал с упрощённого варианта и у меня были большие сложности, поэтому когда у меня наконец заработал простой вариант, я совершенно позабыл что это только начало. Так или иначе держите то что вы хотели

fn main() {
const N:usize=10;
let a = [1; N];
println!("{:?}", a);

// если разкоментить то раст говорит что trait `FromIterator<usize>` is not implemented for `[usize; 10]`
//let a = (0..100).fold(a, |a, i| a.iter().enumerate().map(
let a = (0..100).fold(a.to_vec(), |a, i| a.iter().enumerate().map(
	|(j, &x)| if a[i%N]%N != j { x } else { x + 1 }).collect()
);
println!("{:?}", a);
}

песочница

Да это опять не 100% то что вы хотите (тип а изменился со статического масива на вектор), но это уже детали (arraymap есть в найтли значит нет фундаментальных проблем реализовать этот трайт для array). В любом случае вы просто заставили меня по упражняться в рэнджи, спасибо было интересно посмотреть как оно в расте — неплохо, мне понравилось.

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

// требуется nightly channel
#![feature(array_map)]

fn main() {
const N:usize=10;
let a = [1; N];
println!("{:?}", a);
let a = a.map(|x| x+1);
println!("{:?}", a);
}

[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
Здесь ответ обоим @mayorvp и PsyHaSTe на коменты выше

Да признаю, семантика = в расте не такая как в плюсах. Нет, вы все ещё не правы на счёт let; Вы нашли изъян в моих НЕСУЩЕСТВЕННЫХ тезисах. Вы все еще никак не отреагировали на суть:
  • Полностью выведенный тип переменной не может быть изменён операций присваивания
  • После полного определения любая переменная должна иметь конкретный ТИП, валидное ЗНАЧЕНИЕ и АДРЕС

Если вы все ещё прибываете в заблуждении насчёт что let x; это полное определение (а не часть раздельного), то попытайтесь ответить на такой вопрос — если бы следующий код был бы валидным (а он не компилируется без ошибок), то что по вашему мы бы увидели в консоли?
fn type_of<T>(_: &T) -> &'static str {
    std::any::type_name::<T>()
}

fn main() {
let x;// = 1;
println!("{}, {}, {:p}", type_of(&x), x, &x);
}


Что это за тип такой {integer}

Ответ смотри здесь. Я так понимаю что {integer} это ВНУТРЕННЕЕ обобщенное(не доконца выведенное) представление типа числовой переменной и является деталью реализации компилятора и НИКОГДА не доступно из программы. Иными словами это как конецпт интегрального типа в с++ — шаблонный тип с определенным интерефейсом. В валидной программе тип любой переменной ВСЕГДА будет выведен до самого конца и в случае числовых констант дефолтный выбор это i32.

Если в моем примере выше вы напишите let x = 1; то на консоль быдет распечатано: i32, 1, 0x7ffe490bef0c
Видите никаких {integer} здесь и не пахнет. i32 это валидный КОНКРЕТНЫЙ заместитель обобщеннго {integer} т.к. полностью удовлетворяет всем тербованиям {integer}.

И ещё раз для закрепления — ошибка компилятора о том что тип {intger} в очередной раз подтверждает мой тезис — в валидной программе все типы выведены до конца, неполное объявление, а затем попытка использования приводит к ошибке компилятора где он сообщает максимум известной информации на момент ошибки. Т.е. если удалость понять что это {integer} уже хорошо давайте скажем прогера хоть что то о типе это лучше чем ничего не сказать ни смотря на то что конкретный тип не был выведен.
Вопрос, почему в дофига ФП хачкеле в котором кстати никаких мутаций через = не существует всё равно есть паттерн let… in ...?


Я не настоящий сварщик фпшник, но сочувствующий. Мой мозг так же как и мозг многих других разработчиков сильно пострадал подвержен влиянию с++. Спросите лучше 0xd34df00d говорят он успешно прошёл курс ФП терапии и больше не использует некачественные препараты небезопасные языки.

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


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

На остальные тезисы попробую ответить скопом т.к. они связаны. Я тут подумал и мне кажется я начинаю понимать в чем сложность донести мою мысль до вас, не уверен что все так и есть но думаю как то так: ваша нейросеть сильно прикипела к ключевому слову let и/или аналогам в других языках. Когда я говорю что оно лишнее ваши нейроны кричат что я сошел сума и кто-то пытается вас лишить очень уютного/удобного ментального костыля. Ну и ладно давайте я уважу вашу нейросеть, да и к тому же это неважная деталь для той мысли что я пытаюсь объяснить. Я переформулирую мою мысль с равенством первоначальной до изоморфии.

Я заявляю, что если вы начиная с этой секунды везде и всегда (на расте конечно) где ставите знак равно будете ещё ставить let, то ваш код станет только лучше и вы ничего не потеряете, другими словами добровольно откажитесь от того 1% случав где вы использовали присваивание и вместо него используйте шэдовинг с явным возвратом значение из вложенного скоупа в том случае если мутировали не локальную переменную.
fn main() {
let x = 1;
let y = 2;
println!("{} {}", x, y);  //1, 2
let (x, y) = (3,4);
println!("{} {}", x, y);  //3, 4
}

тоже самое но со вложенным скоупом
fn main() {
let x = 1;
let y = 2;
println!("{} {}", x, y);

let (x, y) = if x != y
{
	let (x, y) = (x + 2, y + 2);
	(x, y)
} else { (x, y) };
println!("{} {}", x, y);
}


Специально проверил оба примера валидный раст код. Обратите внимание на то что отпала необходжимость в mut. Да mut все еще нужен для вызова мутирующих/инплэйс функций, но это опять же несущественные детали не влияющие на суть идеи. Шарпы и другие языки НЕ МОГУТ В ПРИНЦИПЕ реализовать эффективную оптимизацию и заменить переопределение на присваивание, но не потаму что (не)испоьзуется гц, а потаму что раст реализовал уникальную функциональность компилятор раст знает сколько ссылок на объект, это ключевая информация для реализации этой оптимизации. Смотрите одна ссылка значит мутабельный, много значит немутабельный и нельзя оптимизировать — придется делать копии.

И как мы отличим присваивание от мутирования в таком случае?

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

Очень хорошо, что половину пути вы уже прошли:
Да, мутировать когда можно не мутировать и использовать «expression everything» плохо


А теперь пожалуйста покажите пример
только вот не всегда это возможно.


когда я не смогу применить свою идею(везде где есть = добаить let) на расте.

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


Как я уже неоднократно писал, все эти мутации это просто узаконенные оптимизации. Еще раз до эпохи раста это было РАЗУМНО, т.к. не было принципиальной возможности реализовать эти оптимизации на уровне компилятора, не было ключевой информации сколько ссулок на объект. Раст может сделать эту оптимизацию на уровне компилятора и поэтому наличие присваивания (и mut тоже) является ИЗЪЯНОМ дизайна раст. Да я понимаю что разработчики скорее всего не могли этого предвидеть заранее когда делали подсчет ссылок, но никогда не поздно что то улучшить, хотя конечно это то еще ломающее изменение языка и по сути что то типа раст2 или вообще другой язык. Хотя технически вы уже сегодня можете отказаться от многих mut и от всех присваиваний.

Ну а теперь как вишинка на торте, если везде и всегда писать let x = value; и нигде не иметь x = value; то очевидно что нет необходимости в let и можно просто везде писать x = value;

для полноты я проверил еще и мувыбл тип
fn main() {
let x = "x";
let y = "y";
println!("{} {}", x, y);

let (x, y) = if x != y
{
	let (x, y) = (x.to_owned() + "x", y.to_owned() + "y");
	(x, y)
} else { (x.to_owned(), y.to_owned()) };
println!("{} {}", x, y);
}
Ну судя по синтаксису и моим куцим знаниям это питон. Худо-бедно, но об ошибке рапортует. Лучше хоть как то работающий ЯП чем идеальный в вакууме голове дизайнера языка.
я никогда не утверждал, что fn это плохо, по мне так лучше его вообще выпилить или заменить на хаскелевский синтаксис ->, но тут мне хотя бы понятна где возникает неоднозначность и как fn реально помогает её разрулить.

Да мне понятны ваши вопросы, вопросы… Это просто система привычек которые у нас сформировались — использование побочных эффектов.

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

let mut x = 10;
if cond { x = 5; } // это ЗЛО побочный эффект, т.к. x ГЛОБАЛЬНАЯ переменная для if скоупа
x = if cond { 5 } else { x }; // не зло т.к. ПЕРЕОПРЕДЕЛЕНИЕ (может быть с оптимизированно в присваивание). возможно else бранч не обязателен, не знаток раста


Мне НЕ нужно видеть что происходит внутри любого вложенного блока, я точно могу быть уверенным что внутри НИЧЕГО видимого здесь поменяться не может. Также я вижу что x переопределяется и что с этого момента он имеет другое значение.

Сейчас в расте да и в других языках мне НУЖНО заглянуть внутрь вложенного блока что бы узнать, а не мутирует ли он что либо — это и есть нарушение локальности.

В моей вселенной x = 5 ВСЕГДА локален тому скоупу в котором вы его видите. Т.е. не важно есть там наверху переменная с таким именем или нет ее, x = 5 только здесь и сейчас -до конца скоупа или до следующего ПЕРЕОПРЕДЕЛЕНИЯ x в том же скоупе. Скоуп закончился все нет никакого x. Если выше был объявлен другой x то он НИКАК не поменяется это совершенно ДРУГОЙ не связанный х.

Все бест практисы для всех языков советуют просто давать локальным переменным ДРУГИЕ имена, что бы они ничего не хайдили и тем самым добиваться того поведения о котором я пишу — НЕ МУТИРОВАТЬ переменные из вышестоящего скоупа до тех пор пока это не вредит производительности. Проблема в том что давать другие имена не всегда лучшее решение, часто бывает что имена локальных переменных становятся муторнее/менее понятными. Это простительно для языков спроектированных ДО раста.

если мне нужна мутабильность то я буду писать
mut x = Type{5};
что позволит мне вызывать мутирующие методы класса/структуры Type. для переменной x, без mut только лишь не мутирующие методы.

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

var str = "xxx";
str += "x";

+= выглядит как мутирующий оператор, но на самом деле он НЕ мутирующий но создает копию и переопределяет str.

Есть только одна проблема с этим подходом, разработчики C# не осилили сделать оптимизацию которая вместо переопределения в рантайме вызовет специфичный/оптимальный/эффективный код конкатенации. У них была проблема/причина компилятор НЕ знал сколько ссылок есть на этот объект и поэтому они НЕ смогли сделать такую оптимизацию. Раст ЗНАЕТ сколько у него ссылок на это значение и МОЖЕТ реализовать такую оптимизацию когда ссылка ровно одна.

Поэтому для раста это изъян в дизайне, он может (без потери производительности) и должен был бы запретить присваивание как таковое исключая целый ряд потенциальных ошибок как невыразимых.
Ну я же уже продемонстрировал, что цикл с побочным эффектом на внешней по отношению к скоупу цикла переменной легко выражается/заменяется на цикл/рекурсию где НЕТ побочных эффектов для нелокальной для цикла переменной.

Я прошу привести пример, где никак невозможно заменить присваивание на переопределение + явное вынесение побочных эффектов (если они есть).
Мой комментарий может показаться вам слишком резким, и я не хочу обидеть лично вас, но то что вы написали — полная чушь.

Раст это статически типизированный ЯП, а это значит что присваивание НЕ меняет ТИП переменной, тогда как по вашей версии сначала тип unknown, а потом int или еще что то. Также в расте не существует типа unknown. Еще больше, присваивание семантически это всегда создание КОПИИ объекта (не важно глубокой или неглубокой), что бы создать копию, ваш левый операнд в операции присваивания ДОЛЖЕН быть валиден на момент копирования — какой инвариант имеет значение
значения очевидно никакого нет.
? Отсутствие значение это НЕ валидное значение это вообще НЕ значение. Этот как NAN не число или null не значение. Нельзя разыменовывать нулл поинтер. И естественно адреса у такой переменной НЕТ и быть не может, т.к. ни тип ни значение ещё не известны.

Я ещё не достаточно хорошо знаю раст поэтому расскажу на примере с++ — присваивание это такая специальная операция которую можно переопределить — предоставить свою реализацию. Смысл в том что операция может быть вызвана только лишь на уже сконструированном и валидном объекте, но let x; не вызывает НИКАКОГО конструктора не аллоцирует ресурсы и не создаёт валидную переменную, у которой можно было бы вызвать операцию присваивания далее по коду.

Еще разок, объявление это связывание уже сконструированного объекта с именем всегда гарантированно 0 рантайм оверхед.
Присваивание это создание КОПИИ объекта связанного с именем справа от знака равно используя ресурсы объекта связанного с именем слева от оператора =, это либо дефолтная реализация копирование содержимого памяти объекта справа в память объекта слева (предполагаем что они/адреса всегда разные) либо кастомная операция. Надеюсь я смог объяснить почему вы не правы.

Information

Rating
Does not participate
Registered
Activity