All streams
Search
Write a publication
Pull to refresh
5
0
Send message

Это будет медленно там, где есть Result и быстро там, где его нет. При этом там где он есть, проход по ошибке будет примерно так же быстр, как и проход без неё.

Либо указывайте явные типы ошибки (если это нужно), либо, если конкретный тип не важен - приводите к какому-нибудь общему (хоть к Box<dyn std::error::Error, но правильнее использовать уже упомянутые выше anyhow или thiserror).

В расте это ровно так и сделано. Я пытаюсь объяснить, почему это решение (растовское) невозможно реализовать сильно меньшей кровью. Откуда появляется необходимость явно указывать типы в Result, зачем обработка ошибок сделана (синтаксически) так, как сделана, а не иначе и т.д.

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

то дочитаем комментарий о том, что таких функций в коде встречается редко — это во первых (и это причина забить на эту просадку перфа)

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

и во вторых решабельна и задача по данной проблеме.

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

Чтобы что-то подставлять в рантайме, нужны дополнительные расходы.

В расте это сделано на этапе компиляции - можно написать код с Generic-параметром, который работает и для функций Result (и тогда тоже возвращает Result), и для функций возвращающих примитивное значение (и тогда возвращает примитивное значение).

Во время компиляции будут создано несколько реализаций (по одной для каждого использованного типа) и на этапе компиляции будет подставлена правильная.

Растовский Result<T,E> является прямым аналогом значения типа T либо исключения типа E.
Если мы договорились, что каким-то образом по сигнатуре функции нужно уметь понимать, какие эксепшены она вызывает, то дальше это вопрос синтаксический, а не содержательный.

Какую задачу можно решить эксепшенами (при этом, как мы договорились, checked), но нельзя Result?

Во всех них исключения дорогие, см. бенчмарки.

Не важно, кто размечает компилятор/препроцессор/программист или высшие силы.

Если в разметке получилось так, что "не бросающая исключения функция" вызывает "бросающую исключения функцию" и не ловит её ошибку, то эта разметка некорректна.

Если мы все функции высшего порядка помечаем бросающими исключения, как Вы предлагаете вот в этом комментарии: https://habr.com/en/post/709328/#comment_25112510 то любая функция, которая её вызывает и не проверяет вызов на ошибку тоже должна быть помечена как бросающая исключение.

Да. И в итоге всё будет равномерно медленно. Я же ровно об этом. Есть три варианта:

  • Вызовы дешёвые везде, но обработка исключений дорогая

  • Вызовы везде дорогие, зато исключения обрабатываются без дополнительных расходов

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

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

Я не нашёл способа сделать цитату и список одновременно :(

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

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

  1. почему нельзя библиотеки размечать?

  2. если говорить о препроцессоре, то почему нельзя библиотеки размечать по сигнатуре Result<T,E>?

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

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

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

во вторых даже если тупо отказаться от его решения (снизив эффективность)

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

Нет, нельзя. И на это есть две существенные причины:

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

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

Так Раст и справляется, вроде бы. Просто это является (как и время жизни) частью сигнатуры функции. Если функция возвращает Result<T,E>, то это можно читать как "может вернуть T, а может бросить исключение E". Собственно, пробрасывание E наверх (если ты находишься в функции, которая может вернуть E, или, хотя бы, что-то к чему E приводится) делается одним оператором ("?").
Проблема примерно такая - если сама функция помечена как "не бросающая эксепшенов", но при этом она вызывает функцию, которая может бросить, какое ожидается поведение?

Вставлять такую на каждый вызов каждой функции банально дорого.

Либо нужно вставлять только для тех вызовов, для которых нужно, либо, альтернативно, ни для каких не вставлять.

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

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

А как препроцессор узнает, какие функции нужно оборачивать в этот блок, а какие нет?

Не знаю про go, но про Раст это не так.

Проверять на err значение все вызовы дорого (по CPU). Идеология exception'ов: любая функция может выбросить любой эксепшен, но большинство вызовов err-значения возвращать не будет, поэтому exception'ы можно обрабатывать долго.

Другая альтернатива (выбранная Растом) звучит так: в compile time известно какие функции какие exception'ы может выбрасывать и проверять нужно только такие вызовы (а остальные проверять не нужно).

Знание во время компиляции нужно, например, для колбеков: возьмём тривиальную функцию, которая просто сразу зовёт полученный на вход колбек и больше ничего не делает. В Rust это будут разные функции, в зависимости от того, может ли колбэк бросать исключение или нет (и, в зависимости от этого, сама функция будет либо бросать, либо нет). В итоге если использовать первую функцию, то есть гарантии, что ошибки не будет (и проверять на наличие ошибки не нужно), а если вторую - то возможность ошибки появляется (и это видно по сигнатуре функции).

В какой-нибудь Java такого разделения сделать не получается, в итоге либо нужно все непрямые вызовы проверять на ошибки (что дорого, потому что ошибки случаются редко), либо дополнительно платить за обработку каждого исключения.

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

Он не стал менее лаконичным. Он вряд ли поменял эффективность (потому что переходы дословно те же). Зато все условные переходы, по которым можно прийти на err_clk видны из структуры кода (а не из его чтения).

Как минимум от err_pm и err_out избавится не составляет труда, на эти теги ровно по одному условному переходу, можно остальной код унести в else.

Как, кстати, и err_clk.
Т.е. на стандартных if/else оно выглядит вот так:

static int amba_read_periphid(struct amba_device *dev)
{
	struct reset_control *rstc;
	u32 size, pid, cid;
	void __iomem *tmp;
	int i, ret;

	ret = dev_pm_domain_attach(&dev->dev, true);
	if (ret) {
		dev_dbg(&dev->dev, "can't get PM domain: %d\n", ret);
	}
	else {
		ret = amba_get_enable_pclk(dev);
		if (ret) {
			dev_dbg(&dev->dev, "can't get pclk: %d\n", ret);
		}
		else {

			/*
			 * Find reset control(s) of the amba bus and de-assert them.
			 */
			rstc = of_reset_control_array_get_optional_shared(dev->dev.of_node);
			if (IS_ERR(rstc)) {
				ret = PTR_ERR(rstc);
				if (ret != -EPROBE_DEFER)
					dev_err(&dev->dev, "can't get reset: %d\n", ret);
			}
			else {
				reset_control_deassert(rstc);
				reset_control_put(rstc);

				size = resource_size(&dev->res);
				tmp = ioremap(dev->res.start, size);
				if (!tmp) {
					ret = -ENOMEM;
				}
				else {

					/*
					 * Read pid and cid based on size of resource
					 * they are located at end of region
					 */
					for (pid = 0, i = 0; i < 4; i++)
						pid |= (readl(tmp + size - 0x20 + 4 * i) & 255) << (i * 8);
					for (cid = 0, i = 0; i < 4; i++)
						cid |= (readl(tmp + size - 0x10 + 4 * i) & 255) << (i * 8);

					if (cid == CORESIGHT_CID) {
						/* set the base to the start of the last 4k block */
						void __iomem *csbase = tmp + size - 4096;

						dev->uci.devarch = readl(csbase + UCI_REG_DEVARCH_OFFSET);
						dev->uci.devtype = readl(csbase + UCI_REG_DEVTYPE_OFFSET) & 0xff;
					}

					if (cid == AMBA_CID || cid == CORESIGHT_CID) {
						dev->periphid = pid;
						dev->cid = cid;
					}

					if (!dev->periphid)
						ret = -ENODEV;

					iounmap(tmp);
				}
			}
			amba_put_disable_pclk(dev);
		}
		dev_pm_domain_detach(&dev->dev, true);
	}
	return ret;
}

Information

Rating
Does not participate
Registered
Activity