LINUX.ORG.RU

Rust: преобразования указателя на трейт в конкретный тип

 


1

7

Хочу хранить мапу с экземплярами реализующими трейт. При этом на месте одного ключа я уверен, что будет всегда лежать экземпляр одной и той же реализации (в данном примере в качестве ключа выступает помимо прочего TypeId). С хранием проблем не возникает, возникает проблема с извлечением элементов.

use std::collections::HashMap;
use std::any::TypeId;
use std::rc::Rc;

trait MyTrait {
    fn new() -> Self where Self: Sized;

    // Тут ещё какие-нибудь методы
}

struct MyStruct {
    items: HashMap<(i32, TypeId), Rc<dyn MyTrait>>
}

impl MyStruct {
    fn get<T: MyTrait + 'static>(&mut self, key: i32) -> Rc<T> {
        if let Some(item) = self.items.get(&(key, TypeId::of::<T>())) {
            item.clone() as Rc<T>
        } else {
            let item = Rc::new(T::new());
            self.items.insert((key, TypeId::of::<T>()), item.clone());
            item
        }
    }
}

Как правильно конвертировать элемент при его извлечении в тип T?

★★★★★

Последнее исправление: KivApple (всего исправлений: 2)

Посмотри треит std::any::Any, или весь модуль.

use std::any::Any;
use std::rc::Rc;

fn main() {
    let mut foo: Vec<Rc<dyn Any + 'static>> = Vec::new();
    foo.push(Rc::new(12u32));
    foo.push(Rc::new(String::from("Hello")));
    foo.push(Rc::new(3.1415));
    
    for i in &foo {
        if let Some(value) = i.downcast_ref::<u32>() {
            println!("u32: {}", value);
        } else if let Some(value) = i.downcast_ref::<String>() {
            println!("String: {}", value);
        } else if let Some(value) = i.downcast_ref::<f64>() {
            println!("f64: {}", value);
        }
    }
}
anonymous-angler ★☆
()
Ответ на: комментарий от ox55ff

std::variant это аналог сумтипа, которые в расте есть нативно, и проходиться по ним можно с помощью match. ОП хочет немного более странного, ибо dyn MyTrait может содержать любой из неограниченного множества типов, реализующих MyTrait, так что пройтись по ним нельзя.

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

@balsoft

match не является полиморфным. Это просто сахарок над вереницей убогих if (x is T1) { (x as T1).t1() }.

visit является многократно более мощной альтернативой, потому что

  1. Не требует знания типов, хранящихся в варианте

  2. Позволяет полагаться на свойства типов, а не их имена.

Классический пример это

std::variant<a1, a2, a3, b1, b2, c1, c2, d> v;

std::visit(overloaded {
    [](a_like auto && x){ x.a(); },
    [](b_like auto && x){ x.b(); },
    [](auto && x){ /* something else */ }
}, v);

Для Rust пришлось бы писать по меньшей мере 6 веток match, 3 и 2 из которых имели бы одинаковое содержанием, каждый раз явно прописывая ассоциированное с типом содержания имя варианта enum’а:

match v {
    A1(a) => a.a(),
    A2(a) => a.a(),
    A3(a) => a.a(),
    B1(b) => b.b(),
    B2(b) => b.b(),
    _ => { ... } // при этом мы даже не можем извлечь в else-ветке какую-либо информацию  
}

При всем этом visit и variant реализованы полностью в рамках языка, а не являются компиляторным сахаром. Еще раз подчеркну, что match ничем не отличается от цепочки if let, кроме того, что его чуть короче писать. Это убого.

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

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

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

Еще раз подчеркну, что match ничем не отличается от цепочки if let, кроме того, что его чуть короче писать.

Только это if let является сахаром над match, а не наоборот.

Не требует знания типов, хранящихся в варианте

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

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

match не является полиморфным. Это просто сахарок над вереницей убогих if (x is T1) { (x as T1).t1() }.

Ты бы хоть узнал сначала. Match является простым

switch(x.tag){
  case a:
    x.a.t1();
  break;
  ...
}

Никаких is в нем нет.

Это плюсы в виду отсутствия нормальных типов сумм вынуждены извращаться с визитором.

Что до примера с alike/blike - ты определись, тебе надо полиморфизм с наследованием или тип-сумму. Если уж крайне надо напихать 20 разных вариантов с одинаковой обработкой - пили трейты alike, blike, пили дженерик для обработки ветки, имплементь его для alike и blike и копипасть вызов в ветки. Или пиши макрос. Да, это многословно, но это всегда можно придумать неудобный случай.

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

Глупость сказал. У enum выше есть дискриминант, а значит он работает как switch и там нет вереницы if-ов. Она банально не нужна. Так же как и variant, в котором дискриминантом выступает typeid. Что из этого «мощнее» - спорный вопрос, если просто свалить кучу типов - ок, variant лучше (Пока variadic generics не имплементировали). Если что-то более осмысленное, вроде

enum Identificer {
    Id(u32),
    Name(String),
    Uuid(Uuid),
}

То нет, не мощнее.

А на самом деле, всем насрать. Я просто ответил ОП как это можно сделать. Мне реально безразлично как это делается в цпп и почему (:

anonymous-angler ★☆
()
Ответ на: комментарий от trex6

Это загрузчик шейдеров. Идея в том, что для каждой шейдерной программы отдельная структура, которая помимо id шейдерной программы из OpenGL ещё хранит позиции всех uniform и attribute найденные при создании программы через glGetUniformLocation и glGetAttribLocation. Также все шейдерные программы реализуют трейт, который содержит new принимающий id шейдерной программы (на самом деле небольшую обёртку, у которой ещё есть Drop и некоторые другие методы актуальные для любой программы).

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

pub trait ShaderProgramWrapper {
	fn new(program: ShaderProgram) -> Self where Self: Sized;

	fn bind(&self) -> ShaderProgramBinding;
}

pub struct AssetLoader {
	...
	shader_programs: HashMap<(&'static str, &'static str, TypeId), Rc<dyn Any>>
	...
}

impl AssetLoader {
	pub fn load_shader_program<T: ShaderProgramWrapper + 'static>(
		&mut self, 
		vertex_shader_file_name: &'static str, 
		fragment_shader_file_name: &'static str
	) -> Rc<T> {
		let key = (vertex_shader_file_name, fragment_shader_file_name, TypeId::of::<T>());
		if let Some(program) = self.shader_programs.get(&key) {
			program.clone().downcast().unwrap()
		} else {
			let program = Rc::new(T::new(ShaderProgram::new(&[
				&self.load_vertex_shader(vertex_shader_file_name),
				&self.load_fragment_shader(fragment_shader_file_name)
			]).unwrap()));
			self.shader_programs.insert(key, program.clone());
			program
		}
	}
}

Решил проблему через Any.

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 2)
Ответ на: комментарий от anonymous-angler

У enum выше есть дискриминант, а значит он работает как switch и там нет вереницы if-ов. Она банально не нужна.

Это никакого значения не имеет. Я пишу о том, чем этот конструкт является с точки зрения языка, а не о том, как конкретно будет осуществляться диспатч. С точки зрения языка match полностью аналогичен цепочке if let. Все.

Что из этого «мощнее» - спорный вопрос,

Нет, не спорный. Я уже написал, что visit является полиморфным. match – нет.

Еще ты попытался подменить вопрос сравнения visit и match на сравнение variant и enum. Хотя variant можно использовать как enum, так делать не стоит. В принципе enum использовать не стоит.

Если что-то более осмысленное, вроде

struct Identifier: std::variant<Id, Name, Uuid>{};
Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Это никакого значения не имеет. Я пишу о том, чем этот конструкт является с точки зрения языка,

Это как понимать "с точки зрения языка?

Теоретически можно и виртуальные функции реализовать через цепочку if (typeid(val) == typeid(A)) __A_foo(val). Но никто же не говорит, что твой волшебный динамический полиморфизм - это «с точки зрения языка» цепочка if-ов. Так зачем ты врёшь?

Нет, не спорный. Я уже написал, что visit является полиморфным. match – нет.

Теперь дело за малым: придумать почему «полиморфный» лучше чем не полиморфный.

Вообще, чё за срань ты придумал: у тебя уже есть костыльный sum-type из 80х, построенный на базовом типе и наследниках. Он работает с помощью динамического вызова функций, но он очень кривой, требует внедрения «клиентского» кода во все классы и потому для практического применения малопригоден. Вместо него тебе попытались наколхозить нормальный variant, наколхозить так, как язык позволяет, т.е. плохо. С помощью магии насилования компилятора получилось что-то даже не первый взгляд работоспособное и не сильно страшное, но, конечно, гораздо хуже чем в случае нормального языка. Но чтоб сделать вид, что этот колхоз не обычный костыль ты начал лепить кадавра, объединяя 2 этих подхода, причём через задницу. В стиле «мой старый жигуль перед тем как завестись 3 раза пердит, вот повторите мне на своих модных солярисах такое поведение».

Еще ты попытался подменить вопрос сравнения visit и match на сравнение variant и enum.

Так только для этого variant и нужен. Чтоб обработать разные варианты локально, не перетаскивая внутрь «собачка говорит гав»-иерархии клиентский код.

struct Identifier: std::variant<Id, Name, Uuid>{};

Ты забыл реализации струтур Id, Name и Uuid. Ну и мы же понимаем, что в реальном коде у тебя будет std::variant<unsigned, string, Uuid>, потому что так короче или потому что проект старый там изначально не сделали типизацию и ещё куча отговоорок. А потом ты замучаешься, когда появится вариант идентификации по URI, который тоже в стринге

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

Ты бы хоть узнал сначала. Match является простым

Что там узнавать? match в общем случае работает на if, switch частный случай. Даже в расте.

Но т.к. раст не может в тайплевел, то никаких типовых матчей там нет. Есть только днище по дискриминанту. Это ограничение, а не повод для гордости.

Он даже не то что в тайплевел - он не может в any/dyn не может. Потому как там открытые значения для которых нужен if. Поэтому общий случай match - это if. И для значений в расте так же if.

Это плюсы в виду отсутствия нормальных типов сумм вынуждены извращаться с визитором.

типов сумм

Это про раст? Где в enum типы? Это просто сахарок, который не знает ничего о типах.

А в целом все более-менее сложные матчи в расте написаны на визиторе. Наверное они про твои сказки не знают?

Что до примера с alike/blike - ты определись, тебе надо полиморфизм с наследованием или тип-сумму.

Где там «наследование»? Зачем ты пытаешься навязать другим ограничение своего языка?

Не буду брать C++, ведь там нет «тип-сумму». Возу язык, где как раз таки есть настоящие тип-суммы, а не сахарок.

type AB = {tag: 0, foo(): string}|{tag: 1, foo(): number}|{tag: 2, bar(): void};


function foo(x: AB) {
    switch(x.tag) {
        case 0: case 1: x.foo(); break;
        default: x.bar();
    }
}

Всё работает так же. Без всяких трейтов, наследований и прочего.

Да, это многословно, но это всегда можно придумать неудобный случай.

Это базовый случай, а не неудобный. Опять же, язык с настоящими «тип-суммами» - там большинство кода использует показанную выше фичу.

К тому же, ничего из перечисленного тобою не работает. Трейты ограничены одной сигнатурой и не могут хранить данные. Никакие «дженерики» тут не помогут.

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

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

Так же как и variant, в котором дискриминантом выступает typeid.

Нет. Никакого typeid там нет - там есть индекс.

У enum выше есть дискриминант

Выше отвечал на это. В том и проблема, что там есть только дискриминант и нет никаких типов. Именно это тебе и пытались сказать.

variant - тайплевел структура из которой уже получают дискриминант. enum - это только дискриминант, где после сахарок кастует union в нужный тип.

Из того что и там и там есть дискриминант не значит ровным счётом ничего.

Что из этого «мощнее» - спорный вопрос

Да нет, как раз таки вопрос закрытый.

Для начала - нет смысла ограничивать сравнение только вариантом со стороны C++, потому как это лишь малая часть системы типов/мета-возможностей. В расте же ничего этого на уровне типов не выражается - остаётся только enum-сахарок.

Сам сахарок мало во что может. А variant в полном смысле может всё, что данный сахарок.

Если даже взять юнионы из ts, где они не простой сахарок. Там да - уже можно подумать. Но и то только на тему удобства, а не на тему «мощнее».

если просто свалить кучу типов - ок, variant лучше

(Пока variadic generics не имплементировали).

Это невозможно сделать в расте в том виде в котором есть в С++. Придётся выкинуть весь раст. Единственное что можно - сделать видимость, но и то это вряд ли произойдёт в ближайшем будущем. Проблема только в том, что видимость не позволит сделать variant.

Если что-то более осмысленное, вроде

Покажи назначение этого. Для меня это выглядит максимально бессмысленным.

То нет, не мощнее.

Это мёртвый тип. Чтобы он работал - для него нужно делать impl. Каждый раз везде и всюду. Но ты покажи применение - может я его не вижу. impl будет то ещё страшилище не расширяемое.

Поэтому проще сделать

struct Identificer {
  Identificer(u32);
  Identificer(string);
  Identificer(uuid);
}

На него можно накидать методы, делать какие-то общий операции без костылей.

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

Это как понимать "с точки зрения языка?

С того, что у тебя типы не могут. Пример я выше привёл с языком, у которого тип-суммы на уровне типов.

Теоретически можно и виртуальные функции реализовать через цепочку if (typeid(val) == typeid(A)) __A_foo(val). Но никто же не говорит, что твой волшебный динамический полиморфизм - это «с точки зрения языка» цепочка if-ов. Так зачем ты врёшь?

Реализация не имеет отношения к теме. Вот в C++ матчинг происходит на уровне системы типов - типы участвуют при матчинге. Поэтому, допустим, в default попадают остатки. Так же происходит в ts.

У тебя никаких типов нет. У тебя простой сахарок с кастом в case. Сам же match знает что-то только о дискриминанте. В этом его проблема.

Теперь дело за малым: придумать почему «полиморфный» лучше чем не полиморфный.

Скорее «не могущий в типы» и «могущий». Зачем языку мочь в типы объяснять нужно?

Вообще, чё за срань ты придумал: у тебя уже есть костыльный sum-type из 80х

Нет, ты ошибаешься. Именно enum из раста это костыльный сим-тип из 80х, даже ближе к 60х. enum из раста ничего из себя не представляет. Просто наколеночное днище.

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

У тебя какие-то проблемы с пониманием темы. Пониманием что такое открытые/закрытые типы. Ты где-то услышал про «внедрение» и даже не знаешь зачем оно, но зачем-то повторяешь.

Почему оно динамическое? Потому что открытые типы. Тоже самое что dyn в расте. Ты считаешь dyn ненужным?

Почему «внедрение»? Всё так же просто. Там откуда ты брал эти лозунги тебе не рассказали, либо ты не слушал.

Внешняя vtable, которая используется для dyn в расте - это как раз таки самый примитивный и древний вид. Он не может в наследование и прочее. И то для удобства внедрение всё равно нужно - нужно таскать таблицу с собою. Это мало чем отличается от внедрение.

Но почему же в С++ внедрение? Потому что там есть наследование. Причём множественное, в том числе виртуальное. Ничего этого в расте нет. А раз нет, то можно сделать проще.

Именно так и появились трейты, как раз таки в районе тех самых 80х.

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

Смысл идеи прост - мы берём список методов. Берём класс и сбоку навешиваем на него эти методы. После генерируется таблица и кладётся рядом с данными. Эти методы не могут хранить данные в объекте, очевидно.

Подобное в цпп мало кому нужно было, но даже если нужно - оно без проблем реализуется. Библиотек куча.

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

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

Вместо него тебе попытались наколхозить нормальный variant, наколхозить так, как язык позволяет, т.е. плохо.

Это любое 4.2

Объясню что такое вариант. Дело в том, что на самом деле нам практически никогда ненужны enum именно как динамическая сущность. Поэтому в C++ достаточно написать foo(auto x).

В расте же, как и языках без полиморфизма - мы не может это сделать. Поэтому enum используется именно как затычка это дыры.

Такая же проблема есть и в си. А раст никаких фишек относительно си не имеет по части языка. Как написать foo(auto x) в си? Да, можно написать void *, но мы никогда не узнаем тип.

Первый костыль, который мы здесь вводим - это закрытый тип. Т.е. захардкодить все варианты входящих типов. В си это union.

Вот мы создаём union foo_arg {int a; float b; double c;}, что после написать foo(foo_arg x). Конечно, мы не узнаем какой тип лежит. Приходится брать тот самый дискриминант и сувать его ещё одним параметром, либо в агрегат вместе с union.

Но всё это ненужно в C++, мы просто пишем foo(auto). Вот в расте нет этого, как и в си. И приходится писать те же костыли. Только разве что в расте добавили сахарка.

#include<cstdio>

struct foo_arg {
    enum class tag_t {a, b, c} tag;
    union {int a; float b; double c;};
};


void foo(foo_arg x) {
    switch(x.tag) {
        using enum foo_arg::tag_t;
        case a: fprintf(stderr, "%d\n", x.a); break;
        case b: fprintf(stderr, "%f\n", x.b); break;
        case c: fprintf(stderr, "%f\n", x.c); break;
    }
}

Весь сахарок в расте сводится к добавлению в неймсейс switch тегом. Далее как костыль нужно созданий новых переменных. Т.к. оно ничего не знает о типах - мы можем в любом кейсе использовать x.a - тот же ts этого не даст.

Так же сахарок позволяет проще определять foo_arg. В целом всё это можно даже на макросах сишных накостылять. Это максимально примитивный сахар.

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

Теперь на тему варианта, что же такое variant. Выше мы выяснили, что enum в расте это костыль, которым затыкают дыры ввиду отсутствия тайплевел-полиморфизма(далее просто полиморфизм).

Но зачем же в C++ вариант? Вариант в С++ это не enum, и уж тем более не попытка его повторить. Повторять такое днище никому ненужно. Вариант в C++ - это сериализация полиморфизма.

Вот у вас есть функция foo(auto x) и вы хотите её аргументы, допустим, положить в вектор.

Вы хотите сделать magic x; foo(x) - и foo запустится так, как если бы x был статически известным типов. Вот вариант это про это. Как этого добавиться. Какие-то сравнение с enum и прочими костылями - попытка натянуть на С++ свои фантазии.

Первое, что здесь нужно сделать - это закрыть тип. Полиморфизм же в С++ открытый. Т.е. нужен список доступных типов. Но он закрытый относительно системы типов на уровне tu. Поэтому auto не сводится к бесконечности. В рантайме никакой системы типов нет. Её нужно закрыть. Закрывается она на уровне magic.

Вот вариант - это просто произвольный список типов. Для системы типов это typename ..., т.е. локальная закрытая система типов.

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

Просто пример:

#include<cstdio>
#include<cstdlib>

struct a{};
struct b{};

a parse_a() { return {}; }
b parse_b() { return {}; }


auto parser() {
  if(rand()) {
    return parse_a();
  } else {
    return parse_b();
  }
}

void print(auto) {
  fprintf(stderr, "%s\n", __PRETTY_FUNCTION__);
}


int main() {
  auto x = parser();
  print(x);
}


В с++ print мы можем написать на auto. В расте нет - нужно костылять трейты, но трейты не расширяемы и в любом случае я не обсуждаю сейчас трейты.

А вот в случае с parser мы не можем так написать. Мы хотим вернуть a|b, но так нельзя. Поэтому в расте есть enum-костыль. Обычно он тут и будет использоваться.

В C++ здесь можно использовать тот же вариант. Сделав std::variant<a, b> parser(). Но это неважная часть. Но именно на ней сосредотачиваются раст-агитаторы.

Важна как раз таки часть эта:


int main() {
  auto x = parser();
  print(x);
}

Задача варианта сделать так, чтобы это работало. Но мы не можем напрямую вызывать print от варианта(по крайней мере с нужной нам семантикой). Вот здесь и появляется visit, как десериализация полиморфизма.

visit(print, x) - он вызывает функцию как если бы мы напрямую вызывали print(a); print(b);. Передать print напрямую мы не можем из-за костылей - его нужно врапнуть в лямбду.

При этом мы можем добавить


template<typename ... Ts> void print(std::variant<Ts...> x) {
  std::visit([](auto x) { return print(x); }, x);
}

И тогда код будет работать без изменения вообще. В этом фишка. Поэтому знайте, что любой кто пытается вам рассказывать о том, что вариант это какой-то enum, что кто-то пытался повторить enum - это просто невежда, либо попросту врун.

Но основная фишка не в этом. На самом деле полиморфизм может разрешить эту ситуацию и без варианта. Просто в C++ это не работает в силу его ограничений. Но что мы можем сделать?


void parser(auto pass) {
  if(rand()) {
    pass(parse_a());
  } else {
    pass(parse_b());
  }
}



int main() {
  parser([](auto x) {
    print(x);
  });
}

Можно код переписать так. По-сути именно этот фокус вариант и делает. У цпп не работает полиморфизм по возврату, но работает по входу. Мы преобразуем один в другой.

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

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

Ты забыл реализации струтур Id, Name и Uuid.

Нет, он не забыл. Они ненужны. А вот ты пытаешься манипулировать. Создание структур имеет совершенное иную семантику и никакой enum в расте не избавляет тебя от их создания.

Т.е. необходимость в расте явно обмазываться Identifier::Id - это просто немощность системы типов/вывода типов. Ничем полезным оно не наделяя.

Те значения, которым это инициализируется будет не Identifier::Id-типа, а u32. Что позволяет перепутать id с любым другим u32. Если перпутать - это проблема. Нужна структура. Если не проблема - ненужна. Никаким образом костыли из раста эта проблему не решают. А ты пытаешься вызвать синтаксический/семантический мусор за какое-то преимущество.

Ну и мы же понимаем, что в реальном коде у тебя будет std::variant<unsigned, string, Uuid>, потому что так короче

Будет, дальше что? У тебя так же. Если типы нужно разделять - их нужно разделять. Если ненужно - ненужно. Выше подробнее это разобрал.

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

Вот ещё одна байка раст-агитаторов. Почему-то вдруг в С++ будет проект старый, а в расте нет. Удивительно. Там где проект старый - там раста не будет. А там где будет - там будет проект новый.

А потом ты замучаешься, когда появится вариант идентификации по URI, который тоже в стринге

А каким образом это что-то изменит? Это как раз таки дыра в дизайне. Твои костыли из раста никак не помогут здесь.

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

В данном случае в чём претензия. В том, что т.к. раст не умеет в матчинг по типам(т.е. не умеет в типы) - он матчит по рантайм-инту. Т.к. самое донное днище. С++ же матчит именно по типу.

Вот нам сообщаю, что костыль в расте спасает от того, что у нас коллизии в типе. Но это не так. Это ни от чего не спасает - только скрывает проблему.

Потому как нам ничего не мешает при инициализации перепутать name и uri. И будет у нас string uri; Identifier::Name(uri).

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

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

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

нежели что бы изобрести variant.

Вариант это не про variant, а про полиморфизм. Как раз таки имея его весь раст перестаёт быть нужным потому что там всё костыли призванные латать дыры.

Поэтому вариант не существует сам по себе. Вариант это тайплевел список, а далее произвольные операции над ним. А не то что ты под ним представляешь.

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

Это в целом большая проблема тех кто не понимает сути явлений/фич, а запоминает только их названия.

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

Вот, кстати, типичный пример. Это попытка выразить пародию на вариант.

Вот в расте пишется это, что не обобщается, не знает о типах и прочее. Мог бы раст в полиморфизм - мы могли бы написать unpack<u32, f64, string>(any, |x| println("{}: {}", name_of(x), x)).

Это и есть вариант. Вариант - это any, visit - это этот анпак. Только список хранится в самом any, да и свой typeid, потому что система типов там своя.

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

Что там узнавать? match в общем случае работает на if, switch частный случай. Даже в расте.

Человек расстраививался из-за плохого матча, который, по его мнению

if (val is A)
{
  ((A)val).
}
else if ...

Что в такой реализации плохого? Я вижу 2 повода для расстройства

  1. Цепочка if вместо лукапа по таблице
  2. Потенциально долгая операция is.

Оба эти повода для расстройства нелепы, так как enum - это tagged union с целочисленным дискриминантом. Ну а то, как компилятор решит этот switch реализовать, цепочкой проверок ли, или лукапом, или даже двоичным поиском - это дело компилятора.

Есть только днище по дискриминанту. Это ограничение, а не повод для гордости.

Тебе бы нервы полечить, а то в Царя превратишься, будешь рассказывать про ворованные вектора.

он не может в any/dyn не может. Потому как там открытые значения для которых нужен if.

Ты бредишь.

Это про раст? Где в enum типы? Это просто сахарок, который не знает ничего о типах.

Похоже что о типах ничего не знаешь как раз ты, а не сахарок. Так вот, тип переменной, если удариться в теорию, это множество возможных значений этой переменной. Соответственно типом-суммой называется тип переменной, которая может принимать все значения обоих суммируемых типов. Enum в расте как раз и представляет собой тип-сумму.

Что ты там себе надумал про «настоящие типы», которые не «сахарок» - я вообще без понятия.

Где там «наследование»? Зачем ты пытаешься навязать другим ограничение своего языка?

Чувак именно что пытался подсунуть вариант с наследованием. Читай внимательнее.

Не буду брать C++, ведь там нет «тип-сумму». Возу язык, где как раз таки есть настоящие тип-суммы, а не сахарок.

А не надо мне брать никакие «настоящие языки». Раст в типы суммы умеет. Плюсы - тоже да, но совсем уж черезжопно.

Это базовый случай, а не неудобный. Опять же, язык с настоящими «тип-суммами» - там большинство кода использует показанную выше фичу.

Нет, это не базовый случай, а специально подобранный неудобный. Собрать недостатки обоих подходов - это надо постараться.

К тому же, ничего из перечисленного тобою не работает. Трейты ограничены одной сигнатурой и не могут хранить данные. Никакие «дженерики» тут не помогут.

Зачем тебе хранить данные в трейтах?

Используй дженерик чтоб обернуть последовательность команд над одинаковыми с точ. В матче да, придётся копипастить в стиле

match(e){
   A(v) => process(v),
   B(v) => process(v),
}

Или пихай обработчик в трейт и имплементь его по-разному для alike и blike.

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

Реализация не имеет отношения к теме. Вот в C++ матчинг происходит на уровне системы типов - типы участвуют при матчинге. Поэтому, допустим, в default попадают остатки. Так же происходит в ts.

Мне интересно, я один не понимаю что за бред ты несёшь? Ну т.е. ты показываешь на язык, который включил в понятие типа ещё и время жизни объекта и рассказываешь, что он «не может в типы». Причём я не думаю, что ты настолько тупой, что тебе следует показывать пример типа let a: i32 = 1.2, однако что ты понимаешь под «мочь в типы» ты не поясняешь. Ты точно не Царь?

У тебя какие-то проблемы с пониманием темы. Пониманием что такое открытые/закрытые типы. Ты где-то услышал про «внедрение» и даже не знаешь зачем оно, но зачем-то повторяешь.

Знаешь в чём трагедия? Это ты не понимаешь с чем споришь. Причём я описал вполне прозрачно, но до тебя не дошло и ты начал нести какую-то ахинею о том, где хранится vtable, попутно выдавая перлы об очевидных только тебе преимуществах хранения vtable как в плюсах.

На самом деле речь о другом. Вот есть необходимость в типе-сумме. В старых плюсах для этого пилили иерархию наследования. Shape, Circle: Shape, Triangle: Shape, ну ты понял. Ссылка(указатель) на Shape могла исполнять роль типа суммы, ссылаться на любую фигуру. При этом специфичный для каждой формы код приходилось добавлять в виде виртуальной функции в шейпе с переопределением. Это было максимально всрато. Код, который по уму должен был бы быть в одной функции, например в функции сериализации модели в какой-нибудь xml, оказывался разбросан по куче классов. Это я и назвал внедрением клиентского кода в классы. И чтоб это всё хоть как-то в лапшу не превращалось, приходилось пилить ещё овердофига интерфейсов для инверсии зависимостей.

Собственно тогда и пришло понимание, что ООП - говно и нормально работает только в паре областей, а в остальном больше создаёт проблем чем решает.

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

Нет, он не забыл. Они ненужны. А вот ты пытаешься манипулировать. Создание структур имеет совершенное иную семантику и никакой enum в расте не избавляет тебя от их создания.

Как не освобождает?

Вот смотри:

enum Length {
  Cm(f32),
  Inch(f32),
  Pixels(i32)
}

Видишь, я разом создал варианты.

Аналог на плюсах потребовал бы от меня определения классов Cm, Inch, Pixel, для удобства так же конструктор каждого из них и только потом я смог бы красиво запихать их в std::variant. Не то что бы это не решаемо. Но гораздо приятнее когда оно само работает.

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

Что в такой реализации плохого? Я вижу 2 повода для расстройства

Нет, ты неправильно видишь.

Оба эти повода для расстройства нелепы, так как enum - это tagged union с целочисленным дискриминантом. Ну а то, как компилятор решит этот switch реализовать, цепочкой проверок ли, или лукапом, или даже двоичным поиском - это дело компилятора.

Проблема в этом, что это тупой сахарок поверх if/switch. Как я уже выше писал - у тебя в расте switch просто потому, что match совсем дубовый.

Ты бредишь.

Покажи мне match по dyn/any. Не покажешь. Вот в языке иных это есть.

Похоже что о типах ничего не знаешь как раз ты, а не сахарок. Так вот, тип переменной, если удариться в теорию, это множество возможных значений этой переменной. Соответственно типом-суммой называется тип переменной, которая может принимать все значения обоих суммируемых типов. Enum в расте как раз и представляет собой тип-сумму.

Нет, раст ничего не знает о типе переменной. И вообще о типах. Точно так же как в си union ничего не знает о том, что в нём лежит. Для системы типов это новый тип, новое имя типов. Никакая не сумма. Тебя обманули.

Что ты там себе надумал про «настоящие типы», которые не «сахарок» - я вообще без понятия.

Я тебе показал - C++/ts.

Чувак именно что пытался подсунуть вариант с наследованием. Читай внимательнее.

Нет там наследования. Ты всё перепутал.

А не надо мне брать никакие «настоящие языки». Раст в типы суммы умеет. Плюсы - тоже да, но совсем уж черезжопно.

Нет, не умеет. enum в расте ничего не знает о типах. Это убогий сахарок. О чём уже 10 раз тебе сказали. Ты даже сам это утверждал, а теперь почему-то отрицаешь.

Нет, это не базовый случай, а специально подобранный неудобный. Собрать недостатки обоих подходов - это надо постараться.

Да нет у тебя никакого подхода. А так твои рассуждения - просто фа натизм. Тебе показали 2 языка, где это есть. C++/ts - практически весь код на этих языках написан так.

Зачем тебе хранить данные в трейтах?

Затем, что это нужно. Для этого и существует наследование.

Или пихай обработчик в трейт и имплементь его по-разному для alike и blike.

Это мусор. Тебе уже повторяю. Это выглядит как мусор, работает как мусор. Не говоря уже о том, что не полиморфное и нужно что-то реализовывать.

Если у меня есть 2 типа в которых уже есть методы foo - ты мне предлагаешь создать ещё один трейт с методом фу и делать импл для них?

Дак раст ещё такое днище, что не может даже generic этот импл сделать. Ты будешь его копипастить тысячу лет.

Не говоря уже о том, что в C++ всё это может принимать/возвращать разные типы. В расте этого нет.

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

Аналог на плюсах потребовал бы от меня определения классов Cm, Inch, Pixel, для удобства так же конструктор каждого из них и только потом я смог бы красиво запихать их в std::variant. Не то что бы это не решаемо. Но гораздо приятнее когда оно само работает.

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

Тебе сообщают, что твой Length - это дырявое днище. Но т.к. твой язык не может в типы - ему нормально. Но от реальной коллизии это не избавляет. Т.е. я могу в коде спокойно путать cm/inch.


 let cm: f32 = 123.1f;
 Length::Inch(cm);

Это будет работать, но это ошибка. Поэтому сувать одинаковые типы в enum - это дно. Тебе нужно не ставить это на флаг - это позор, а не преимущество.

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

Чтобы такой дыры не было - тебе нужно как раз таки создавать разные классы для cm/inch, чтобы их не путать. Когда они используются вне enum.

К тому же, такой enum в С++ ненужен. Он нужен тебе потому, что нет полиморфизма. И ты не можешь написать нормальную функцию общую для всех размерностей без костылей подобных.

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

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

Всё что нам нужно сделать о условию задачи - вернуть А или Б. Ну так блин, верни А или Б. Чтоб можно было взять и по-человечески возврат обработать. Вместо этого мне предлагается писать отдельно вот такую срань:

template<typename ... Ts> void print(std::variant<Ts...> x) {
  std::visit([](auto x) { return print(x); }, x);
}

И всё ради чего? Ради того чтоб можно было скрыть за неявным поведением

int main() {
  auto x = parser();
  print(x);
}

Просто классическое #define TRUE FALSE /* happy debugging */

Причём никакой проблемы сделать такое же говно на расте нет, просто заимплементь std::fmt::Display для своего enum-типа.

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

Мне интересно, я один не понимаю что за бред ты несёшь? Ну т.е. ты показываешь на язык, который включил в понятие типа ещё и время жизни объекта и рассказываешь, что он «не может в типы».

Раст не может в типы - там системы типов самое донное днище. Ни какое «время жизни» он никуда не включал. Это твои фантазии. Этого ничего не на уровне типов - это такой примитивный статический анализатор. Уровень его не далеко ушёл от лабы.

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

Причём я не думаю, что ты настолько тупой, что тебе следует показывать пример типа let a: i32 = 1.2, однако что ты понимаешь под «мочь в типы» ты не поясняешь. Ты точно не Царь?

И что ты мне показал? Где тут типы? Ты мне про примитивную ошибку? Это начальная школа. Самая днище из днища. В этой умеет любой язык.

Хотя такое днище даже в сишке 50 лет назад не считали за состоятельное. Поэтому расширили преобразованием типов. А то что ты показываешь - это тупое strcmp-днище. Это заря языкостроения. Наверное в 50 на лабе это бы ещё прокатило за инновацию.

Знаешь в чём трагедия? Это ты не понимаешь с чем споришь. Причём я описал вполне прозрачно, но до тебя не дошло и ты начал нести какую-то ахинею о том, где хранится vtable, попутно выдавая перлы об очевидных только тебе преимуществах хранения vtable как в плюсах.

А можно конкретику, а не оправдания? Где неправ, в чём неправ.

На самом деле речь о другом. Вот есть необходимость в типе-сумме.

Нет такого. И в расте нет никаких тип-сумм. Это сахарок примитивный.

В старых плюсах для этого пилили иерархию наследования.

Не для этого.

Shape, Circle: Shape, Triangle: Shape, ну ты понял.

Нет. Это отрытые типы, это интерфейсы. enum-днище-сахарок из 60-70 это вообще никак не заменяет.

Ссылка(указатель) на Shape могла исполнять роль типа суммы, ссылаться на любую фигуру.

Нет. Ты, опять же, перепутал тот же ts и своё днище. Вот в ts с настоящими сумтипами, которые типы. Так это действительно работает. У тебя нет.

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

Куда переопределением? Ты не знаешь что это такое. Ничего там переопределять ненужно. Переопределение это тогда, когда ты используешь базовую функцию. А когда нет - у функции нет определения. Её нельзя переопределить.

Это было максимально всрато.

Да ты чё.

Код, который по уму должен был бы быть в одной функции, например в функции сериализации модели в какой-нибудь xml, оказывался разбросан по куче классов.

xml - это про раст, я правильно понимаю? Кому и зачем это нужно? Это какое-то легаси-днище из 90.

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

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

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

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

Да ты чё. Что-то кроме оправданий будет?

Всё что нам нужно сделать о условию задачи - вернуть А или Б. Ну так блин, верни А или Б.

Верни. Покажи это на расте.

Чтоб можно было взять и по-человечески возврат обработать. Вместо этого мне предлагается писать отдельно вот такую срань:

Какую срань? Срань - это код на расте. Что ты собрался обрабатывать?

И всё ради чего? Ради того чтоб можно было скрыть за неявным поведением

Каким неявным поведением? Оно как раз таки явное. И да, ты обнови уже лозунги. «неявно поведение» уже опозорилось, когда в расте начали обмазываться into. Поэтому всё. Устарели твои лозунги.

Причём никакой проблемы сделать такое же говно на расте нет, просто заимплементь std::fmt::Display для своего enum-типа.

Показывай. Причём тут твой std::fmt::Display? print - это условное название. Там произвольная логика.

В результате что мы видим? Я ничего не пишу, а ты мне предлагаешь писать мусор? В этом и смысл.

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

Кстати, удивительно то как твой фанатизм работает в взаимоисключащими параграфами.

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

Дак вот, полиморфизм в C++ работает именно так же как трейты. А match работает как наследование. У тебя здесь противоречие - тебе нужно починить. Почему в одном случае ты защищаешь одно, а в другом другое.

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

Проблема в этом, что это тупой сахарок поверх if/switch. Как я уже выше писал - у тебя в расте switch просто потому, что match совсем дубовый.

С каких пор простота и эффективность реализации стала в программизме недостатком?

Покажи мне match по dyn/any. Не покажешь. Вот в языке иных это есть.

Так раст - это не иные. В нём матч сделан для того, для чего нужно, а не для динамического приведения типов.

Проблема не в Расте, проблема вот в этих твоих «иных» языках, которые, в виду отсутствия в них нормальных типов сумм, вынуждены колхозить это через наследование.

Точно так же как в си union ничего не знает о том, что в нём лежит.

Ты бредишь. Как не знает, если как раз знает? Ну или закеж код, где ты кладёшь одно и достаёшь другое.

Для системы типов это новый тип, новое имя типов. Никакая не сумма.

Вот это правда, да новый тип. Но это не имеет значения. Ты же можешь создать в плюсах тип-произведение с помощью простой структурки. И если до тебя докопаются, что твой Point2d - это неправильное произведение 2х Coordinate, ты пошлёшь нахрен.

Да нет у тебя никакого подхода.

Подходы не у меня, а у всех. Если тебе надо хранить А или Б ты можешь колхозить иерархию наследования или просто взять enum/variant. Эти подходы. И вот предложенная идея с объединением - это как раз дурь несусветная.

Я тебе показал - C++/ts.

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

Короче, ты мне надоел.

Я не готов к долгой дискуссии с аргументацией «Раст не может в типы» и «Смотри сам как в C++/ts и догадайся что я хотел сказать». Если ты не в состоянии сформулировать свою мысль внятно, то давай без меня.

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

Вот match это тоже самое, что наследование.

Чё?

Ты рассказываешь, что трейты лучше.

Лучше чем что? Что ты несёшь вообще?

Дак вот, полиморфизм в C++ работает именно так же как трейты.

Фейспалм. Чувак, не пользуйся термином «полиморфизм». Он хорош в рекламе. Был. 40 лет назад. В технологической беседе эта хрень слишком двусмысленна и хз что ты хочешь этим словом сообщить. Да, трейты используются в том числе и для того чтоб реализовать часть того, что принято называть полиморфизмом.

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

Собственно тогда и пришло понимание, что ООП - говно и нормально работает только в паре областей, а в остальном больше создаёт проблем чем решает.

Вот, эти пустые нелепые сказки. Не осилил ооп - расскажи о том, что «создаёт проблемы».

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

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

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

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

Но в целом С++ идёт в сторону от виртуальных методов. Поэтому никто их добавление в цпп особо не лоббирует.

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

С каких пор простота и эффективность реализации стала в программизме недостатком?

Это не простота и эффективность. Это просто сахарок над древним сишным паттерном.

Так раст - это не иные. В нём матч сделан для того, для чего нужно, а не для динамического приведения типов.

Нет. В нём нет матча. Там где матч сделан(для тех же значений) - он работает в общем случае именно как if. А вот там где копипаста+сахарок на сишной древностью - там да, это немощное днище не может ни во что.

Проблема не в Расте, проблема вот в этих твоих «иных» языках, которые, в виду отсутствия в них нормальных типов сумм, вынуждены колхозить это через наследование.

Причём тут наследование? Ты хочешь сказать, что в расте ненужен any? dyn traits? Вон автор как раз решает проблему за счёт any.

Зачем ты повторяешь какую-то чушь про наследование - ты не знаешь что это и зачем.

Ты бредишь. Как не знает, если как раз знает? Ну или закеж код, где ты кладёшь одно и достаёшь другое.

С чего вдруг это стало критерием? Это не критерий. Вот из any тоже взять другое нельзя. Только толку.

Система типов раста ничего не знает ни о каких тип-суммах. Это просто днище-сахарок. А то, что сахарок может позволять «не взять то, что не поклал» - это достигается за счёт рантайма, а не системы типов.

Вот это правда, да новый тип. Но это не имеет значения.

Имеет. Ты выдавал это за тип-сумму. Но это просто мусорный тип.

Ты же можешь создать в плюсах тип-произведение с помощью простой структурки.

Не прям полность, но в целом С++ знает о том, что лежит у него в структурах. По крайней мере там есть интроспекция. У тебя же нет.

И если до тебя докопаются, что твой Point2d - это неправильное произведение 2х Coordinate, ты пошлёшь нахрен.

Ну да, ты слился. Придумал какие-то шизо-определения. Потом мне их навязываешь.

Подходы не у меня, а у всех. Если тебе надо хранить А или Б ты можешь колхозить иерархию наследования или просто взять enum/variant.

enum - это днище сахарок. enum не заменяет наследование/иерархию. variant не имеет никакого отношения к enum. Это совершенно про иное. Вот он может заменить наследование, а твоё днище нет.

Как может заменить его реальный тип-суммы в ts. Твой днище-сахарок - это позорище.

Эти подходы. И вот предложенная идея с объединением - это как раз дурь несусветная.

АРгументация.

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

Какое определение? Покой голове? Тебе дали определения. Тебе дали примеры даже на языках, которые могут. Ты всё игнорируешь и навязываешь мне свой днище-сахарок.

Я не готов к долгой дискуссии с аргументацией «Раст не может в типы» и «Смотри сам как в C++/ts и догадайся что я хотел сказать». Если ты не в состоянии сформулировать свою мысль внятно, то давай без меня.

Нет, не можешь. Ты уже 20 раз слился. А теперь ещё раз пытаешься. Тебе был продемонстрирован даже код. Где всё демонстрируется.

Очевидно, что ты всё игнорируешь, врёшь и продолжаешь врать.

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

Чё?

То.

Лучше чем что? Что ты несёшь вообще?

Лучше чем всё, по крайней мере наследование.

Фейспалм. Чувак, не пользуйся термином «полиморфизм».

Да, потому что ты в него не можешь

Он хорош в рекламе. Был. 40 лет назад.

Ты перепутал. Полиморфизм это не то, что ты нагуглил и прочитал по первой ссылке в википедии.

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

В технической беседе люди знают предмет. И знаю что такое полиморфизм в контексте С++.

Да, трейты используются в том числе и для того чтоб реализовать часть того, что принято называть полиморфизмом.

Нет, в этом твоя проблемы. Ты не знаешь предмета. Твои знания о теме в райней джуна, который о полиморфизме знает из днище-методичек про какое-то ооп.

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

Да ты чё. Что-то кроме оправданий будет?

Да какое это оправдание? Ты написал хрень. Там где в расте нормальный человек напишет match, а в плюсах длинный и замусоренный аналог на визите ты просто перегрузил функцию принт.

Верни. Покажи это на расте.

Ты не хуже меня знаешь. Через енум.

Какую срань? Срань - это код на расте. Что ты собрался обрабатывать

Нет, бро, срань - это твой код.

Каким неявным поведением? Оно как раз таки явное.

Оно неявное, потому что весь смысл твоего примера состоял в том, чтоб скрыть что возвращает парсер. Ну молодец, тебе это удалось. Только нужно ли было это делать?

Показывай. Причём тут твой std::fmt::Display

Ну ты хотел чтоб принт работал, я тебе объяснил как сделать чтоб он работал. Если нужно другое - оно делается аналогично.

Пишешь какую-нибудь хрень типа

impl Fooable for MyEnum
{
  fn foo(&self) {
    match self  {
      MyEnum::A(v) => v.foo(),
      MyEnum::B(v) => v.foo()
    }
  }
}

На всякий случай ещё раз проговариваю:

  1. То что ты делал - это тупо. Не надо так делать. Нигде, ни в плюсах, ни на расте.

  2. То что ты делал - это не какое-то суперуникальное свойство плюсов. Примерно такого же результата можно добиться на расте через трейты. Но, опять же, повторяю: можно - не значит нужно.

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

Какое определение? Покой голове? Тебе дали определения. Тебе дали примеры даже на языках, которые могут. Ты всё игнорируешь и навязываешь мне свой днище-сахарок.

Нет, мне дали нытьё про то какой раст плохой и днище и больше ничего. Причём почему плохой - хз, просто плохой и всё. Потому что трейты в нём из 90х, не модно.

Давай так, напрягись, и роди определение. Только без слов «сахарок», «днище» и т.п.

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

Давая я тебе объясню. Авось узнаешь что-то.

Вот ты выше рассказывал сказки про «чтобы написать что-то - мне нужно пихать это в каждый класс». Как раз таки трейды и решают это. Чтобы писать внешние методы. Опять же здесь возникают проблемы с видимостью, с наследованием. Но раст-днище слишком примитивное для этого. Предположим, что этих проблем нет.

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

Вот в расте этого нельзя. В C++ я могу написать print(auto) в раст-днище нет. Я могу написать print<T>(_:T), но это тоже днище. Я не могу в специализацию. Ну и в целом это параметрическое днище.

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

А теперь возьмём match, мы где-то его напишем. А теперь добавить в enum новый элемент. Что нам придётся делать? Правильно - патчить все использования в match этого enum. Проблема аналогичная наследованию.

В случае с вариантом - нам ненужно ничего делать. Мы просто пишем новую перегрузку для функции. А используется это всё через visit.

Чтобы достичь того же в расте - нужно заменить enum+match на трейты. На самом деле так в расте те, кто по умнее, либо тех кого требования обязывают.

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

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

Да какое это оправдание? Ты написал хрень. Там где в расте нормальный человек напишет match, а в плюсах длинный и замусоренный аналог на визите ты просто перегрузил функцию принт.

Нет, длинный и замусоренный - это про раст. В С++ это никому ненужно. Там есть полиморфизм.

Ты не хуже меня знаешь. Через енум.

Вот, а C++ ненужен никакой enum в 95% случаев. А у тебя нужен.

Нет, бро, срань - это твой код.

Да ты чё.

Оно неявное, потому что весь смысл твоего примера состоял в том, чтоб скрыть что возвращает парсер. Ну молодец, тебе это удалось. Только нужно ли было это делать?

Я не скрывал то, что возвращает парсер. Я превратил прозрачно существующий в с++ код, полиморфный. В то, что можно сериализовать в рантайм. При том кода я не менял.

А ты будешь пердолится и писать подобный мусор:

impl Fooable for MyEnum
{
  fn foo(&self) {
    match self  {
      MyEnum::A(v) => v.foo(),
      MyEnum::B(v) => v.foo()
    }
  }
}

А смысл моего примера состоял в том, что в С++ ненужно всё это. А даже если нужно - выше указано.

Ну ты хотел чтоб принт работал, я тебе объяснил как сделать чтоб он работал. Если нужно другое - оно делается аналогично.

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

Пишешь какую-нибудь хрень типа

Не, ты всё перепутал. Эта хрень не возвращать ничего. Тебе нужно сделать ещё один enum. Не говоря уже о том, что тормозящие рантайм-днище дырявое.

То что ты делал - это тупо. Не надо так делать. Нигде, ни в плюсах, ни на расте.

Делают. Все.

То что ты делал - это не какое-то суперуникальное свойство плюсов. Примерно такого же результата можно добиться на расте через трейты. Но, опять же, повторяю: можно - не значит нужно.

Нельзя. Раст - это днище из 60. Там система типов нулевая. Оно ничего не может. Но ты можешь показать.

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

Нет, мне дали нытьё про то какой раст плохой и днище и больше ничего. Причём почему плохой - хз, просто плохой и всё. Потому что трейты в нём из 90х, не модно.

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

Давай так, напрягись, и роди определение. Только без слов «сахарок», «днище» и т.п.

Определение чего?

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

Давая я тебе объясню. Авось узнаешь что-то.

У тебя слишком большое самомнение. Пока что твоё объяснение зачем нужен вариант закончилось черезжопной перегрузкой функции print.

Вот ты выше рассказывал сказки про «чтобы написать что-то - мне нужно пихать это в каждый класс».

Да. Только почему «сказки»?

Как раз таки трейды и решают это. Чтобы писать внешние методы.

Проблема в том, что в плюсах ней трейтов. Но так да, это было бы не очень хорошим, но решением.

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

Ну вот я даю тебе Shape* ptr. Сохрани его в новомодный формат xml. Shape, напоминаю, это базовый класс.

Вот в расте этого нельзя. В C++ я могу написать print(auto) в раст-днище нет. Я могу написать print(_:T), но это тоже днище. Я не могу в специализацию. Ну и в целом это параметрическое днище.

У тебя это, днище прорвало.

Поэтому в С++ внешние не-виртуальные методы таковыми и писали. В расте приходится костылят через днище-трейты. Это просто ограничение.

Ну конечно же нет.

enum Shape{
  Line(...)
  Circle(...),
}


fn WriteToXml(Shape shape) {
  match shape {
  ...
  }
}

Видишь как классно, всё в одном месте. Не раскидано по нескольким трейтам, и тем более на напихано внутрь Shape.cpp, Circle.cpp, Line.cpp. Если мне надо рисовать эти шейпы в разных графических библиотеках мне не придётся извращаться и пилить 100500 перегрузок draw в каждом классе.

Это тебе не auto писать, тут мозги нужны.

А теперь добавить в enum новый элемент. Что нам придётся делать? Правильно - патчить все использования в match этого enum. Проблема аналогичная наследованию.

Да ты шо? Чё, правда если добавить новый вариант то нужно везде где с ним работали этот вариант предусмотреть?

В случае с вариантом - нам ненужно ничего делать. Мы просто пишем новую перегрузку для функции. А используется это всё через visit.

Чувак, уже до тебя написали что визит очень хорош и им можно несколько веток одной строчкой покрыть. Никто с этим не спорит. Вот только хорошо ли это? А если семантически «то же решение» не подходит, а компилятор тебя даже не предупредил?

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

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

Определение чего?

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

khrundel ★★★★
()